1 #include <SDL2/SDL.h>
2 #include <stdlib.h>
3 #include <stdio.h>
4 #include <string.h>
5 #include <math.h>
6 
7 #include "video/surface.h"
8 #include "video/video.h"
9 #include "game/scenes/arena.h"
10 #include "audio/stream.h"
11 #include "audio/audio.h"
12 #include "audio/music.h"
13 #include "game/utils/settings.h"
14 #include "game/objects/har.h"
15 #include "game/objects/scrap.h"
16 #include "game/objects/hazard.h"
17 #include "game/objects/arena_constraints.h"
18 #include "game/protos/object.h"
19 #include "game/utils/score.h"
20 #include "game/game_player.h"
21 #include "game/game_state.h"
22 #include "game/utils/ticktimer.h"
23 #include "game/gui/text_render.h"
24 #include "resources/languages.h"
25 #include "game/gui/menu.h"
26 #include "game/gui/menu_background.h"
27 #include "game/gui/textbutton.h"
28 #include "game/gui/textselector.h"
29 #include "game/gui/textslider.h"
30 #include "game/gui/label.h"
31 #include "game/gui/filler.h"
32 #include "game/gui/frame.h"
33 #include "game/gui/progressbar.h"
34 #include "controller/controller.h"
35 #include "controller/net_controller.h"
36 #include "resources/ids.h"
37 #include "utils/log.h"
38 #include "utils/random.h"
39 
40 #define TEXT_COLOR color_create(186,250,250,255)
41 
42 #define HAR1_START_POS 110
43 #define HAR2_START_POS 211
44 
45 typedef struct arena_local_t {
46     guiframe *game_menu;
47 
48     surface sur;
49     int menu_visible;
50     unsigned int state;
51     int ending_ticks;
52 
53     component *health_bars[2];
54     component *endurance_bars[2];
55 
56     chr_score player1_score;
57     chr_score player2_score;
58 
59     int round;
60     int rounds;
61     int over;
62 
63     object *player_rounds[2][4];
64 
65     int rein_enabled;
66 
67     sd_rec_file *rec;
68     int rec_last[2];
69 } arena_local;
70 
71 void arena_maybe_sync(scene *scene, int need_sync);
72 void write_rec_move(scene *scene, game_player *player, int action);
73 
74 // -------- Local callbacks --------
75 
game_menu_quit(component * c,void * userdata)76 void game_menu_quit(component *c, void *userdata) {
77     scene *s = userdata;
78     chr_score_reset(game_player_get_score(game_state_get_player((s)->gs, 0)), 1);
79     chr_score_reset(game_player_get_score(game_state_get_player((s)->gs, 1)), 1);
80     game_state_set_next(s->gs, SCENE_MENU);
81 }
82 
game_menu_return(component * c,void * userdata)83 void game_menu_return(component *c, void *userdata) {
84     arena_local *local = scene_get_userdata((scene*)userdata);
85     game_player *player1 = game_state_get_player(((scene*)userdata)->gs, 0);
86     controller_set_repeat(game_player_get_ctrl(player1), 1);
87     local->menu_visible = 0;
88     game_state_set_paused(((scene*)userdata)->gs, 0);
89     arena_maybe_sync(userdata, 1);
90 }
91 
arena_music_slide(component * c,void * userdata,int pos)92 void arena_music_slide(component *c, void *userdata, int pos) {
93     music_set_volume(pos/10.0f);
94 }
95 
arena_sound_slide(component * c,void * userdata,int pos)96 void arena_sound_slide(component *c, void *userdata, int pos) {
97     sound_set_volume(pos/10.0f);
98 }
99 
arena_speed_slide(component * c,void * userdata,int pos)100 void arena_speed_slide(component *c, void *userdata, int pos) {
101     scene *sc = userdata;
102     game_state_set_speed(sc->gs, pos + 5);
103 }
104 
scene_fight_anim_done(void * userdata)105 void scene_fight_anim_done(void *userdata) {
106     object *parent = userdata;
107     scene *scene = game_state_get_scene(parent->gs);
108     arena_local *arena = scene_get_userdata(scene);
109 
110     // This will release HARs for action
111     arena->state = ARENA_STATE_FIGHTING;
112 
113     // Custom object finisher callback requires that we
114     // mark object as finished manually, if necessary.
115     //parent->animation_state.finished = 1;
116 }
117 
scene_fight_anim_start(void * userdata)118 void scene_fight_anim_start(void *userdata) {
119     // Start FIGHT animation
120     game_state *gs = userdata;
121     scene *scene = game_state_get_scene(gs);
122     animation *fight_ani = &bk_get_info(&scene->bk_data, 10)->ani;
123     object *fight = malloc(sizeof(object));
124     object_create(fight, gs, fight_ani->start_pos, vec2f_create(0,0));
125     object_set_stl(fight, bk_get_stl(&scene->bk_data));
126     object_set_animation(fight, fight_ani);
127     //object_set_finish_cb(fight, scene_fight_anim_done);
128     game_state_add_object(gs, fight, RENDER_LAYER_TOP, 0, 0);
129     ticktimer_add(&scene->tick_timer, 24, scene_fight_anim_done, fight);
130 }
131 
scene_ready_anim_done(object * parent)132 void scene_ready_anim_done(object *parent) {
133     // Wait a moment before loading FIGHT animation
134     ticktimer_add(&game_state_get_scene(parent->gs)->tick_timer, 10, scene_fight_anim_start, parent->gs);
135 
136     // Custom object finisher callback requires that we
137     // mark object as finished manually, if necessary.
138     parent->animation_state.finished = 1;
139 }
140 
scene_youwin_anim_done(object * parent)141 void scene_youwin_anim_done(object *parent) {
142     // Custom object finisher callback requires that we
143     // mark object as finished manually, if necessary.
144     parent->animation_state.finished = 1;
145 }
146 
scene_youwin_anim_start(void * userdata)147 void scene_youwin_anim_start(void *userdata) {
148     // Start FIGHT animation
149     game_state *gs = userdata;
150     scene *scene = game_state_get_scene(gs);
151     animation *youwin_ani = &bk_get_info(&scene->bk_data, 9)->ani;
152     object *youwin = malloc(sizeof(object));
153     object_create(youwin, gs, youwin_ani->start_pos, vec2f_create(0,0));
154     object_set_stl(youwin, bk_get_stl(&scene->bk_data));
155     object_set_animation(youwin, youwin_ani);
156     object_set_finish_cb(youwin, scene_youwin_anim_done);
157     game_state_add_object(gs, youwin, RENDER_LAYER_MIDDLE, 0, 0);
158 
159     // This will release HARs for action
160     /*arena->state = ARENA_STATE_ENDING;*/
161 }
162 
scene_youlose_anim_done(object * parent)163 void scene_youlose_anim_done(object *parent) {
164     // Custom object finisher callback requires that we
165     // mark object as finished manually, if necessary.
166     parent->animation_state.finished = 1;
167 }
168 
scene_youlose_anim_start(void * userdata)169 void scene_youlose_anim_start(void *userdata) {
170     // Start FIGHT animation
171     game_state *gs = userdata;
172     scene *scene = game_state_get_scene(gs);
173     animation *youlose_ani = &bk_get_info(&scene->bk_data, 8)->ani;
174     object *youlose = malloc(sizeof(object));
175     object_create(youlose, gs, youlose_ani->start_pos, vec2f_create(0,0));
176     object_set_stl(youlose, bk_get_stl(&scene->bk_data));
177     object_set_animation(youlose, youlose_ani);
178     object_set_finish_cb(youlose, scene_youlose_anim_done);
179     game_state_add_object(gs, youlose, RENDER_LAYER_MIDDLE, 0, 0);
180 
181     // This will release HARs for action
182     /*arena->state = ARENA_STATE_ENDING;*/
183 }
184 
arena_repeat_controller(void * userdata)185 void arena_repeat_controller(void *userdata) {
186     game_state *gs = userdata;
187     game_player *player1 = game_state_get_player(gs, 0);
188     controller_set_repeat(game_player_get_ctrl(player1), 1);
189 }
190 
is_netplay(scene * scene)191 int is_netplay(scene *scene) {
192     if(game_state_get_player(scene->gs, 0)->ctrl->type == CTRL_TYPE_NETWORK ||
193             game_state_get_player(scene->gs, 1)->ctrl->type == CTRL_TYPE_NETWORK) {
194         return 1;
195     }
196     return 0;
197 }
198 
is_singleplayer(scene * scene)199 int is_singleplayer(scene *scene) {
200     if(game_state_get_player(scene->gs, 1)->ctrl->type == CTRL_TYPE_AI) {
201         return 1;
202     }
203     return 0;
204 }
205 
is_demoplay(scene * scene)206 int is_demoplay(scene *scene) {
207     if(game_state_get_player(scene->gs, 0)->ctrl->type == CTRL_TYPE_AI &&
208        game_state_get_player(scene->gs, 1)->ctrl->type == CTRL_TYPE_AI) {
209         return 1;
210     }
211     return 0;
212 }
213 
is_twoplayer(scene * scene)214 int is_twoplayer(scene *scene) {
215     if (!is_demoplay(scene) && !is_netplay(scene) && !is_singleplayer(scene)) {
216         return 1;
217     }
218     return 0;
219 }
220 
arena_screengrab_winner(scene * sc)221 void arena_screengrab_winner(scene* sc) {
222     game_state *gs = sc->gs;
223 
224     // take victory pose screenshot for the newsroom
225     har *h1 = object_get_userdata(game_state_get_player(gs, 0)->har);
226     if(h1->state == STATE_VICTORY || h1->state == STATE_DONE) {
227         har_screencaps_capture(
228             &game_state_get_player(gs, 0)->screencaps,
229             game_state_get_player(gs, 0)->har,
230             SCREENCAP_POSE);
231     } else {
232         har_screencaps_capture(
233             &game_state_get_player(gs, 1)->screencaps,
234             game_state_get_player(gs, 1)->har,
235             SCREENCAP_POSE);
236     }
237 }
238 
arena_end(scene * sc)239 void arena_end(scene *sc) {
240     game_state *gs = sc->gs;
241     int next_id;
242 
243     // Switch scene
244     if (is_demoplay(sc)) {
245         do {
246             next_id = rand_arena();
247         } while(next_id == sc->id);
248         game_state_set_next(gs, next_id);
249     }
250     else if (is_singleplayer(sc)) {
251         game_state_set_next(gs, SCENE_NEWSROOM);
252     } else if (is_twoplayer(sc)) {
253         game_state_set_next(gs, SCENE_MELEE);
254     } else {
255         game_state_set_next(gs, SCENE_MENU);
256     }
257 }
258 
arena_reset(scene * sc)259 void arena_reset(scene *sc) {
260     arena_local *local = scene_get_userdata(sc);
261     local->round++;
262     local->state = ARENA_STATE_STARTING;
263 
264     // Kill all hazards and projectiles
265     game_state_clear_hazards_projectiles(sc->gs);
266 
267     // Initial har data
268     vec2i pos[2];
269     int dir[2] = {OBJECT_FACE_RIGHT, OBJECT_FACE_LEFT};
270     pos[0] = vec2i_create(HAR1_START_POS, ARENA_FLOOR);
271     pos[1] = vec2i_create(HAR2_START_POS, ARENA_FLOOR);
272 
273     // init HARs
274     for(int i = 0; i < 2; i++) {
275         // Declare some vars
276         game_player *player = game_state_get_player(sc->gs, i);
277         object *har_obj = game_player_get_har(player);
278         har *h = object_get_userdata(har_obj);
279         h->state = STATE_STANDING;
280         har_set_ani(har_obj, ANIM_IDLE, 1);
281         h->health = h->health_max;
282         h->endurance = h->endurance_max;
283         h->air_attacked = 0;
284         object_set_pos(har_obj, pos[i]);
285         object_set_vel(har_obj, vec2f_create(0, 0));
286         object_set_gravity(har_obj, 1);
287         object_set_direction(har_obj, dir[i]);
288         chr_score_clear_done(&player->score);
289     }
290 
291     sc->bk_data.sound_translation_table[3] = 23 + local->round; // NUMBER
292     // ROUND animation
293     animation *round_ani = &bk_get_info(&sc->bk_data, 6)->ani;
294     object *round = malloc(sizeof(object));
295     object_create(round, sc->gs, round_ani->start_pos, vec2f_create(0,0));
296     object_set_stl(round, sc->bk_data.sound_translation_table);
297     object_set_animation(round, round_ani);
298     object_set_finish_cb(round, scene_ready_anim_done);
299     game_state_add_object(sc->gs, round, RENDER_LAYER_TOP, 0, 0);
300 
301     // Round number
302     animation *number_ani = &bk_get_info(&sc->bk_data, 7)->ani;
303     object *number = malloc(sizeof(object));
304     object_create(number, sc->gs, number_ani->start_pos, vec2f_create(0,0));
305     object_set_stl(number, sc->bk_data.sound_translation_table);
306     object_set_animation(number, number_ani);
307     object_select_sprite(number, local->round);
308     object_set_sprite_override(number, 1);
309     game_state_add_object(sc->gs, number, RENDER_LAYER_TOP, 0, 0);
310 }
311 
arena_maybe_sync(scene * scene,int need_sync)312 void arena_maybe_sync(scene *scene, int need_sync) {
313     game_state *gs = scene->gs;
314     game_player *player1 = game_state_get_player(gs, 0);
315     game_player *player2 = game_state_get_player(gs, 1);
316 
317     if(need_sync
318         && gs->role == ROLE_SERVER
319         && (player1->ctrl->type == CTRL_TYPE_NETWORK || player2->ctrl->type == CTRL_TYPE_NETWORK)) {
320 
321         // some of the moves did something interesting and we should synchronize the peer
322         serial ser;
323         serial_create(&ser);
324         game_state_serialize(scene->gs, &ser);
325         if (player1->ctrl->type == CTRL_TYPE_NETWORK) {
326             controller_update(player1->ctrl, &ser);
327         }
328         if (player2->ctrl->type == CTRL_TYPE_NETWORK) {
329             controller_update(player2->ctrl, &ser);
330         }
331         serial_free(&ser);
332     }
333 }
334 
arena_har_take_hit_hook(int hittee,af_move * move,scene * scene)335 void arena_har_take_hit_hook(int hittee, af_move *move, scene *scene) {
336     chr_score *score;
337     chr_score *otherscore;
338     object *hit_har;
339     har *h;
340 
341     if (is_netplay(scene) && scene->gs->role == ROLE_CLIENT) {
342         return; // netplay clients do not keep score
343     }
344 
345     if (hittee == 1) {
346         score = game_player_get_score(game_state_get_player(scene->gs, 0));
347         otherscore = game_player_get_score(game_state_get_player(scene->gs, 1));
348         hit_har = game_player_get_har(game_state_get_player(scene->gs, 1));
349     } else {
350         score = game_player_get_score(game_state_get_player(scene->gs, 1));
351         otherscore = game_player_get_score(game_state_get_player(scene->gs, 0));
352         hit_har = game_player_get_har(game_state_get_player(scene->gs, 0));
353     }
354     h = hit_har->userdata;
355     if (h->state == STATE_RECOIL) {
356         DEBUG("COMBO!");
357     }
358     chr_score_hit(score, move->points);
359     chr_score_interrupt(otherscore, object_get_pos(hit_har));
360     arena_maybe_sync(scene, 1);
361 }
362 
arena_har_recover_hook(int player_id,scene * scene)363 void arena_har_recover_hook(int player_id, scene *scene) {
364     chr_score *score;
365     object *o_har;
366 
367     if (is_netplay(scene) && scene->gs->role == ROLE_CLIENT) {
368         return; // netplay clients do not keep score
369     }
370 
371     if (player_id == 0) {
372         score = game_player_get_score(game_state_get_player(scene->gs, 1));
373         o_har = game_player_get_har(game_state_get_player(scene->gs, 1));
374     } else {
375         score = game_player_get_score(game_state_get_player(scene->gs, 0));
376         o_har = game_player_get_har(game_state_get_player(scene->gs, 0));
377     }
378     if(chr_score_end_combo(score, object_get_pos(o_har))) {
379         arena_maybe_sync(scene, 1);
380     }
381 }
382 
arena_har_hit_wall_hook(int player_id,int wall,scene * scene)383 void arena_har_hit_wall_hook(int player_id, int wall, scene *scene) {
384     object *o_har = game_player_get_har(game_state_get_player(scene->gs, player_id));
385     har *h = object_get_userdata(o_har);
386 
387     int towards_wall = 0;
388     if(wall == 0 && o_har->vel.x <= 1) {
389         towards_wall = 1;
390     }
391     if(wall == 1 && o_har->vel.x >= 1) {
392         towards_wall = 1;
393     }
394 
395     int on_air = 0;
396     if(o_har->pos.y < ARENA_FLOOR - 10) {
397         on_air = 1;
398     }
399 
400     // The limit here is entirely guesswork, and might not be it at all
401     // However, it is a close enough guess.
402     // TODO: Find out how this really works.
403     int took_enough_damage = 0;
404     if(h->last_damage_value > 15) {
405         took_enough_damage = 1;
406     }
407 
408     /*
409      * When hitting the lightning arena wall, the HAr needs to get hit by lightning thingy.
410      */
411     if (scene->id == SCENE_ARENA2
412         && on_air
413         && (h->state == STATE_FALLEN || h->state == STATE_RECOIL)
414         && towards_wall
415         && !h->is_grabbed
416         && took_enough_damage)
417     {
418         DEBUG("hit lightning wall %d", wall);
419         h->state = STATE_WALLDAMAGE;;
420 
421         // Spawn wall animation
422         bk_info *info = bk_get_info(&scene->bk_data, 20+wall);
423         object *obj = malloc(sizeof(object));
424         object_create(obj, scene->gs, info->ani.start_pos, vec2f_create(0,0));
425         object_set_stl(obj, scene->bk_data.sound_translation_table);
426         object_set_animation(obj, &info->ani);
427         if(game_state_add_object(scene->gs, obj, RENDER_LAYER_BOTTOM, 1, 0) == 0) {
428 
429             // spawn the electricity on top of the HAR
430             // TODO this doesn't track the har's position well...
431             info = bk_get_info(&scene->bk_data, 22);
432             object *obj2 = malloc(sizeof(object));
433             object_create(obj2, scene->gs, vec2i_create(o_har->pos.x, o_har->pos.y), vec2f_create(0, 0));
434             object_set_stl(obj2, scene->bk_data.sound_translation_table);
435             object_set_animation(obj2, &info->ani);
436             object_attach_to(obj2, o_har);
437             object_dynamic_tick(obj2);
438             game_state_add_object(scene->gs, obj2, RENDER_LAYER_TOP, 0, 0);
439         } else {
440             object_free(obj);
441             free(obj);
442         }
443         return;
444     }
445 
446     /**
447       * On arena wall, the wall needs to pulse. Handle it here
448       */
449     if (scene->id == SCENE_ARENA4
450         && on_air
451         && (h->state == STATE_FALLEN || h->state == STATE_RECOIL)
452         && towards_wall
453         && !h->is_grabbed
454         && took_enough_damage)
455     {
456         DEBUG("hit desert wall %d", wall);
457         h->state = STATE_WALLDAMAGE;
458 
459         // desert always shows the 'hit' animation when you touch the wall
460         bk_info *info = bk_get_info(&scene->bk_data, 20+wall);
461         object *obj = malloc(sizeof(object));
462         object_create(obj, scene->gs, info->ani.start_pos, vec2f_create(0,0));
463         object_set_stl(obj, scene->bk_data.sound_translation_table);
464         object_set_animation(obj, &info->ani);
465         object_set_custom_string(obj, "brwA1-brwB1-brwD1-brwE0-brwD4-brwC2-brwB2-brwA2");
466         if(game_state_add_object(scene->gs, obj, RENDER_LAYER_BOTTOM, 1, 0) != 0) {
467             object_free(obj);
468             free(obj);
469         }
470     }
471 
472     /**
473       * On all other arenas, the HAR needs to hit the wall with dust flying around
474       */
475     if(scene->id != SCENE_ARENA4
476         && scene->id != SCENE_ARENA2
477         && on_air
478         && towards_wall
479         && (h->state == STATE_FALLEN || h->state == STATE_RECOIL)
480         && !h->is_grabbed
481         && took_enough_damage)
482     {
483         DEBUG("hit dusty wall %d", wall);
484         h->state = STATE_WALLDAMAGE;
485 
486         int amount = rand_int(2) + 3;
487         for(int i = 0; i < amount; i++) {
488             int variance = rand_int(20) - 10;
489             int anim_no = rand_int(2) + 24;
490             DEBUG("XXX anim = %d, variance = %d", anim_no, variance);
491             int pos_y = o_har->pos.y - object_get_size(o_har).y + variance + i*25;
492             vec2i coord = vec2i_create(o_har->pos.x, pos_y);
493             object *dust = malloc(sizeof(object));
494             object_create(dust, scene->gs, coord, vec2f_create(0,0));
495             object_set_stl(dust, scene->bk_data.sound_translation_table);
496             object_set_animation(dust, &bk_get_info(&scene->bk_data, anim_no)->ani);
497             game_state_add_object(scene->gs, dust, RENDER_LAYER_MIDDLE, 0, 0);
498         }
499 
500         // Wallhit sound
501         float d = ((float)o_har->pos.x) / 640.0f;
502         float pos_pan = d - 0.25f;
503         sound_play(68, 1.0f, pos_pan, 2.0f);
504     }
505 
506     /**
507       * Handle generic collision stuff
508       */
509     if(on_air
510         && towards_wall
511         && !h->is_grabbed
512         && took_enough_damage
513         && (h->state == STATE_FALLEN
514             || h->state == STATE_RECOIL
515             || h->state == STATE_WALLDAMAGE))
516     {
517         // Set hit animation
518         object_set_animation(o_har, &af_get_move(h->af_data, ANIM_DAMAGE)->ani);
519         object_set_repeat(o_har, 0);
520         scene->gs->screen_shake_horizontal = 3*fabsf(o_har->vel.x);
521         // from MASTER.DAT
522         if(wall == 1) {
523             object_set_custom_string(o_har, "hQ10-x-3Q5-x-2L5-x-2M900");
524             o_har->vel.x = -2;
525         } else {
526             object_set_custom_string(o_har, "hQ10-x3Q5-x2L5-x2M900");
527             o_har->vel.x = 2;
528         }
529 
530         object_dynamic_tick(o_har);
531 
532         if(wall == 1) {
533             o_har->pos.x = ARENA_RIGHT_WALL - 2;
534             object_set_direction(o_har, OBJECT_FACE_RIGHT);
535         } else {
536             o_har->pos.x = ARENA_LEFT_WALL + 2;
537             object_set_direction(o_har, OBJECT_FACE_LEFT);
538         }
539     }
540 }
541 
arena_har_defeat_hook(int player_id,scene * scene)542 void arena_har_defeat_hook(int player_id, scene *scene) {
543     game_state *gs = scene->gs;
544     arena_local *local = scene_get_userdata(scene);
545     int other_player_id = abs(player_id - 1);
546     game_player *player_winner = game_state_get_player(scene->gs, other_player_id);
547     game_player *player_loser = game_state_get_player(scene->gs, player_id);
548     object *winner  = game_player_get_har(player_winner);
549     object *loser   = game_player_get_har(player_loser);
550     har *winner_har = object_get_userdata(winner);
551     // XXX need a smarter way to detect if a player is networked or local
552     if(player_winner->ctrl->type != CTRL_TYPE_NETWORK &&
553             player_loser->ctrl->type == CTRL_TYPE_NETWORK) {
554         scene_youwin_anim_start(scene->gs);
555     } else if(player_winner->ctrl->type == CTRL_TYPE_NETWORK &&
556             player_loser->ctrl->type != CTRL_TYPE_NETWORK) {
557         scene_youlose_anim_start(scene->gs);
558     } else {
559         if (!is_singleplayer(scene)) {
560             // XXX in two player mode, "you win" should always be displayed
561             scene_youwin_anim_start(scene->gs);
562         } else {
563             if (player_id == 1) {
564                 scene_youwin_anim_start(scene->gs);
565             } else {
566                 scene_youlose_anim_start(scene->gs);
567             }
568         }
569     }
570     chr_score *score = game_player_get_score(game_state_get_player(gs, other_player_id));
571     object_select_sprite(local->player_rounds[other_player_id][score->rounds], 0);
572     score->rounds++;
573     if (score->rounds >= ceil(local->rounds/2.0f)) {
574         har_set_ani(winner, ANIM_VICTORY, 0);
575         chr_score_victory(score, winner_har->health);
576         winner_har->state = STATE_VICTORY;
577         local->over = 1;
578         if (is_singleplayer(scene)) {
579           player_winner->sp_wins |= 2 << player_loser->pilot_id;
580           if (player_loser->pilot_id == 10) {
581               // can't scrap/destruct kreissack
582               winner_har->state = STATE_DONE;
583               // major go boom
584               har_set_ani(loser, 47, 1);
585           }
586         }
587     } else {
588         har_set_ani(winner, ANIM_VICTORY, 0);
589         // can't do scrap/destruct except on final round
590         winner_har->state = STATE_DONE;
591     }
592     winner_har->executing_move = 1;
593     object_set_vel(loser, vec2f_create(0, 0));
594     object_set_vel(winner, vec2f_create(0, 0));
595     //object_set_gravity(loser, 0);
596     arena_maybe_sync(scene,
597             chr_score_interrupt(score, object_get_pos(winner)));
598 }
599 
arena_maybe_turn_har(int player_id,scene * scene)600 void arena_maybe_turn_har(int player_id, scene* scene) {
601     int other_player_id = abs(player_id - 1);
602     object *obj_har1 = game_player_get_har(game_state_get_player(scene->gs, player_id));
603     object *obj_har2 = game_player_get_har(game_state_get_player(scene->gs, other_player_id));
604     if (obj_har1->pos.x > obj_har2->pos.x) {
605         object_set_direction(obj_har1, OBJECT_FACE_LEFT);
606     } else {
607         object_set_direction(obj_har1, OBJECT_FACE_RIGHT);
608     }
609 
610     // there isn;t an idle event hook, so do the best we can...
611     har *har2 = obj_har2->userdata;
612     if ((har2->state == STATE_STANDING || har_is_crouching(har2) || har_is_walking(har2)) && !har2->executing_move) {
613         object_set_direction(obj_har2, object_get_direction(obj_har1) * -1);
614     }
615 }
616 
arena_har_hook(har_event event,void * data)617 void arena_har_hook(har_event event, void *data) {
618     scene *scene = data;
619     int other_player_id = abs(event.player_id - 1);
620     arena_local *arena = scene_get_userdata(scene);
621     chr_score *score = game_player_get_score(game_state_get_player(scene->gs, event.player_id));
622     object *obj_har1 = game_player_get_har(game_state_get_player(scene->gs, event.player_id));
623     object *obj_har2 = game_player_get_har(game_state_get_player(scene->gs, other_player_id));
624     har *har1 = obj_har1->userdata;
625     har *har2 = obj_har2->userdata;
626     switch (event.type) {
627         case HAR_EVENT_WALK:
628             arena_maybe_turn_har(event.player_id, scene);
629             break;
630         case HAR_EVENT_AIR_TURN:
631             arena_maybe_turn_har(event.player_id, scene);
632             break;
633         case HAR_EVENT_TAKE_HIT:
634             if(af_get_move(har2->af_data, obj_har2->cur_animation->id)->category != CAT_CLOSE) {
635                 arena_maybe_turn_har(event.player_id, scene);
636             }
637             arena_har_take_hit_hook(event.player_id, event.move, scene);
638             break;
639         case HAR_EVENT_HIT_WALL:
640             arena_har_hit_wall_hook(event.player_id, event.wall, scene);
641             break;
642         case HAR_EVENT_ATTACK:
643             if(object_is_airborne(obj_har1)) {
644                 har1->air_attacked = 1;
645                 DEBUG("AIR ATTACK %u", event.player_id);
646             } else {
647                 // XXX this breaks the backwards razor spin and anything else using the 'ar' tag, so lets disable it for now
648                 //arena_maybe_turn_har(event.player_id, scene);
649             }
650             break;
651         case HAR_EVENT_LAND:
652             if (har2->state == STATE_STANDING || har_is_crouching(har2) || har_is_walking(har2) || har2->executing_move) {
653                 // if the other HAR is jumping or recoiling, don't flip the direction. This specifically is to fix jaguar ending up facing backwards after an overhead throw.
654                 arena_maybe_turn_har(event.player_id, scene);
655             }
656             arena_maybe_sync(scene, 1);
657             DEBUG("LAND %u", event.player_id);
658             break;
659         case HAR_EVENT_AIR_ATTACK_DONE:
660             har1->air_attacked = 0;
661             arena_maybe_sync(scene, 1);
662             DEBUG("AIR_ATTACK_DONE %u", event.player_id);
663             break;
664         case HAR_EVENT_RECOVER:
665             arena_har_recover_hook(event.player_id, scene);
666             if(!object_is_airborne(obj_har1)) {
667                 arena_maybe_turn_har(event.player_id, scene);
668                 DEBUG("RECOVER %u", event.player_id);
669             }
670             break;
671         case HAR_EVENT_DEFEAT:
672             arena_har_defeat_hook(event.player_id, scene);
673             if (arena->state != ARENA_STATE_ENDING) {
674                 arena->ending_ticks = 0;
675                 arena->state = ARENA_STATE_ENDING;
676             }
677             break;
678         case HAR_EVENT_SCRAP:
679             chr_score_scrap(score);
680             break;
681         case HAR_EVENT_DESTRUCTION:
682             chr_score_destruction(score);
683             DEBUG("DESTRUCTION!");
684             break;
685         case HAR_EVENT_DONE:
686             chr_score_done(score);
687             DEBUG("DONE!");
688             break;
689     }
690 }
691 
maybe_install_har_hooks(scene * scene)692 void maybe_install_har_hooks(scene *scene) {
693     object *obj_har1,*obj_har2;
694     obj_har1 = game_player_get_har(game_state_get_player(scene->gs, 0));
695     obj_har2 = game_player_get_har(game_state_get_player(scene->gs, 1));
696     har *har1, *har2;
697     har1 = obj_har1->userdata;
698     har2 = obj_har2->userdata;
699 
700     if (scene->gs->role == ROLE_CLIENT) {
701         game_player *_player[2];
702         for(int i = 0; i < 2; i++) {
703             _player[i] = game_state_get_player(scene->gs, i);
704         }
705         if(game_player_get_ctrl(_player[0])->type == CTRL_TYPE_NETWORK) {
706             har_install_action_hook(har2, &net_controller_har_hook, _player[0]->ctrl);
707         }
708         if(game_player_get_ctrl(_player[1])->type == CTRL_TYPE_NETWORK) {
709             har_install_action_hook(har1, &net_controller_har_hook, _player[1]->ctrl);
710         }
711     }
712 
713     har_install_hook(har1, &arena_har_hook, scene);
714     har_install_hook(har2, &arena_har_hook, scene);
715 }
716 
717 
718 // -------- Scene callbacks --------
719 
arena_free(scene * scene)720 void arena_free(scene *scene) {
721     arena_local *local = scene_get_userdata(scene);
722 
723     game_state_set_paused(scene->gs, 0);
724 
725     if (local->rec) {
726         write_rec_move(scene, game_state_get_player(scene->gs, 0), ACT_STOP);
727         sd_rec_save(local->rec, scene->gs->init_flags->rec_file);
728         sd_rec_free(local->rec);
729         free(local->rec);
730     }
731 
732     for(int i = 0; i < 2; i++) {
733         game_player *player = game_state_get_player(scene->gs, i);
734         game_player_set_har(player, NULL);
735         //game_player_set_ctrl(player, NULL);
736         controller_set_repeat(game_player_get_ctrl(player), 0);
737 
738         for (int j = 0; j < 4; j++) {
739             if (j < ceil(local->rounds / 2.0f)) {
740                 free(local->player_rounds[i][j]);
741             }
742         }
743     }
744 
745     guiframe_free(local->game_menu);
746     surface_free(&local->sur);
747 
748     music_stop();
749 
750     // Free bar components
751     for(int i = 0; i < 2; i++) {
752         component_free(local->health_bars[i]);
753         component_free(local->endurance_bars[i]);
754     }
755 
756     settings_save();
757 
758     free(local);
759 }
760 
write_rec_move(scene * scene,game_player * player,int action)761 void write_rec_move(scene *scene, game_player *player, int action) {
762     arena_local *local = scene_get_userdata(scene);
763     sd_rec_move move;
764     if (!local->rec) {
765         return;
766     }
767 
768     move.tick = scene->gs->tick;
769     move.lookup_id = 2;
770     move.player_id = 0;
771     move.action = 0;
772 
773     if (player == game_state_get_player(scene->gs, 1)) {
774         move.player_id = 1;
775     }
776 
777     if (action & ACT_PUNCH) {
778         move.action |= SD_ACT_PUNCH;
779     }
780 
781     if (action & ACT_KICK) {
782         move.action |= SD_ACT_KICK;
783     }
784 
785     if (action & ACT_UP) {
786         move.action |= SD_ACT_UP;
787     }
788 
789     if (action & ACT_DOWN) {
790         move.action |= SD_ACT_DOWN;
791     }
792 
793     if (action & ACT_LEFT) {
794         move.action |= SD_ACT_LEFT;
795     }
796 
797     if (action & ACT_RIGHT) {
798         move.action |= SD_ACT_RIGHT;
799     }
800 
801     if (local->rec_last[move.player_id] == move.action) {
802         return;
803     }
804     local->rec_last[move.player_id] = move.action;
805 
806     int ret;
807 
808     if ((ret = sd_rec_insert_action(local->rec, local->rec->move_count, &move)) != SD_SUCCESS) {
809         DEBUG("recoding move failed %d", ret);
810     }
811 }
812 
arena_handle_events(scene * scene,game_player * player,ctrl_event * i)813 int arena_handle_events(scene *scene, game_player *player, ctrl_event *i) {
814     int need_sync = 0;
815     arena_local *local = scene_get_userdata(scene);
816     if (i) {
817         do {
818             if(i->type == EVENT_TYPE_ACTION && i->event_data.action == ACT_ESC &&
819                     player == game_state_get_player(scene->gs, 0)) {
820                 // toggle menu
821                 local->menu_visible = !local->menu_visible;
822                 game_state_set_paused(scene->gs, local->menu_visible);
823                 need_sync = 1;
824                 controller_set_repeat(game_player_get_ctrl(player), !local->menu_visible);
825                 controller_set_repeat(game_player_get_ctrl(game_state_get_player(scene->gs, 1)), !local->menu_visible);
826                 DEBUG("local menu %d, controller repeat %d", local->menu_visible, game_player_get_ctrl(player)->repeat);
827             } else if(i->type == EVENT_TYPE_ACTION && local->menu_visible &&
828                     (player->ctrl->type == CTRL_TYPE_KEYBOARD || player->ctrl->type == CTRL_TYPE_GAMEPAD) &&
829                     i->event_data.action != ACT_ESC && /* take AST_ESC only from player 1 */
830                     !is_demoplay(scene)
831               ) {
832                 DEBUG("menu event %d", i->event_data.action);
833                 // menu events
834                 guiframe_action(local->game_menu, i->event_data.action);
835             } else if(i->type == EVENT_TYPE_ACTION) {
836                 if (player->ctrl->type == CTRL_TYPE_NETWORK) {
837                     do {
838                         object_act(game_player_get_har(player), i->event_data.action);
839                         write_rec_move(scene, player, i->event_data.action);
840                     // Rewritten this way, we possible skipped some events before.
841                     // We check if there is a next event, then check if it is EVENT_TYPE_ACTION
842                     // and only then we move the event iterator.
843                     // If conditions fail then we move to the next element at the end of the loop as usual.
844                     // This change also simplified the loop condition, we now don't need to check i for NULL.
845                     } while (i->next && i->next->type == EVENT_TYPE_ACTION && (i = i->next));
846                     // always trigger a synchronization, since if the client's move did not actually happen, we want to rewind them ASAP
847                     need_sync = 1;
848                 } else {
849                     need_sync += object_act(game_player_get_har(player), i->event_data.action);
850                     write_rec_move(scene, player, i->event_data.action);
851                 }
852             } else if (i->type == EVENT_TYPE_SYNC) {
853                 DEBUG("sync");
854                 game_state_unserialize(scene->gs, i->event_data.ser, player->ctrl->rtt);
855                 maybe_install_har_hooks(scene);
856             } else if (i->type == EVENT_TYPE_CLOSE) {
857                 if (player->ctrl->type == CTRL_TYPE_REC) {
858                     game_state_set_next(scene->gs, SCENE_NONE);
859                 } else {
860                     game_state_set_next(scene->gs, SCENE_MENU);
861                 }
862                 return 0;
863             }
864         } while((i = i->next));
865     }
866     return need_sync;
867 }
868 
arena_spawn_hazard(scene * scene)869 void arena_spawn_hazard(scene *scene) {
870     iterator it;
871     hashmap_iter_begin(&scene->bk_data.infos, &it);
872     hashmap_pair *pair = NULL;
873 
874     if (is_netplay(scene) && scene->gs->role == ROLE_CLIENT) {
875         // only the server spawns hazards
876         return;
877     }
878 
879     int changed = 0;
880 
881     while((pair = iter_next(&it)) != NULL) {
882         bk_info *info = (bk_info*)pair->val;
883         if(info->probability > 1) {
884             if (rand_int(info->probability) == 1) {
885                 // TODO don't spawn it if we already have this animation running
886                 object *obj = malloc(sizeof(object));
887                 object_create(obj, scene->gs, info->ani.start_pos, vec2f_create(0,0));
888                 object_set_stl(obj, scene->bk_data.sound_translation_table);
889                 object_set_animation(obj, &info->ani);
890                 if (scene->id == SCENE_ARENA3 && info->ani.id == 0) {
891                     // XXX fire pit orb has a bug whwre it double spawns. Use a custom animation string to avoid it
892                     // it mioght be to do with the 'mp' tag, which we don't currently understand
893                     object_set_custom_string(obj, "Z3-mx+160my+100m15mp10Z1-Z300");
894                 }
895                 /*object_set_spawn_cb(obj, cb_scene_spawn_object, (void*)scene);*/
896                 /*object_set_destroy_cb(obj, cb_scene_destroy_object, (void*)scene);*/
897                 hazard_create(obj, scene);
898                 if (game_state_add_object(scene->gs, obj, RENDER_LAYER_BOTTOM, 1, 0) == 0) {
899                     object_set_layers(obj, LAYER_HAZARD|LAYER_HAR);
900                     object_set_group(obj, GROUP_PROJECTILE);
901                     object_set_userdata(obj, &scene->bk_data);
902                     if (info->ani.extra_string_count > 0) {
903                         // For the desert, there's a bunch of extra animation strgins for
904                         // the different plane formations.
905                         // Pick one, rather than always use the first
906 
907                         int r = rand_int(info->ani.extra_string_count);
908                         if (r > 0) {
909                             str *s = vector_get(&info->ani.extra_strings, r);
910                             object_set_custom_string(obj, str_c(s));
911                         }
912                     }
913 
914                     // XXX without this, the object does not unserialize correctly in netplay
915                     object_dynamic_tick(obj);
916 
917                     DEBUG("Arena tick: Hazard with probability %d started.", info->probability, info->ani.id);
918                     changed++;
919                 } else {
920                     object_free(obj);
921                     free(obj);
922                 }
923             }
924         }
925     }
926 
927     arena_maybe_sync(scene, changed);
928 }
929 
arena_dynamic_tick(scene * scene,int paused)930 void arena_dynamic_tick(scene *scene, int paused) {
931     arena_local *local = scene_get_userdata(scene);
932     game_state *gs = scene->gs;
933     game_player *player1 = game_state_get_player(gs, 0);
934     game_player *player2 = game_state_get_player(gs, 1);
935 
936     if(!paused) {
937         object *obj_har[2];
938         har *hars[2];
939         for(int i = 0; i < 2; i++) {
940             obj_har[i] = game_player_get_har(game_state_get_player(scene->gs, i));
941             hars[i] = obj_har[i]->userdata;
942         }
943 
944         // Handle scrolling score texts
945         chr_score_tick(game_player_get_score(game_state_get_player(scene->gs, 0)));
946         chr_score_tick(game_player_get_score(game_state_get_player(scene->gs, 1)));
947 
948         // Set and tick all proggressbars
949         for(int i = 0; i < 2; i++) {
950             float hp = (float)hars[i]->health / (float)hars[i]->health_max;
951             float en = (float)hars[i]->endurance / (float)hars[i]->endurance_max;
952             progressbar_set_progress(local->health_bars[i], hp * 100);
953             progressbar_set_progress(local->endurance_bars[i], en * 100);
954             progressbar_set_flashing(local->endurance_bars[i], (en * 100 < 50), 8);
955             component_tick(local->health_bars[i]);
956             component_tick(local->endurance_bars[i]);
957         }
958 
959         // RTT stuff
960         hars[0]->delay = ceil(player2->ctrl->rtt / 2.0f);
961         hars[1]->delay = ceil(player1->ctrl->rtt / 2.0f);
962 
963         // Endings and beginnings
964         if(local->state != ARENA_STATE_ENDING && local->state != ARENA_STATE_STARTING) {
965             settings *setting = settings_get();
966             if (setting->gameplay.hazards_on) {
967                 arena_spawn_hazard(scene);
968             }
969         }
970         if(local->state == ARENA_STATE_ENDING) {
971             chr_score *s1 = game_player_get_score(game_state_get_player(scene->gs, 0));
972             chr_score *s2 = game_player_get_score(game_state_get_player(scene->gs, 1));
973             if (player_frame_isset(obj_har[0], "be")
974                 || player_frame_isset(obj_har[1], "be")
975                 || chr_score_onscreen(s1)
976                 || chr_score_onscreen(s2)) {
977             } else {
978                 local->ending_ticks++;
979             }
980             if(local->ending_ticks == 18) {
981                 arena_screengrab_winner(scene);
982             }
983             if(local->ending_ticks > 20) {
984                 if (!local->over) {
985                     arena_reset(scene);
986                 } else {
987                     arena_end(scene);
988                 }
989             }
990         }
991 
992         // Pour some rein!
993         if(local->rein_enabled) {
994             if(rand_float() > 0.65f) {
995                 vec2i pos = vec2i_create(rand_int(NATIVE_W), -10);
996                 for(int harnum = 0;harnum < game_state_num_players(gs);harnum++) {
997                     object *h_obj = game_state_get_player(gs, harnum)->har;
998                     har *h = object_get_userdata(h_obj);
999                     // Calculate velocity etc.
1000                     float rv = rand_float() - 0.5f;
1001                     float velx = rv;
1002                     float vely = -12 * sin(0 / 2 + rv);
1003 
1004                     // Make sure scrap has somekind of velocity
1005                     // (to prevent floating scrap objects)
1006                     if(vely < 0.1 && vely > -0.1) vely += 0.21;
1007 
1008                     // Create the object
1009                     object *scrap = malloc(sizeof(object));
1010                     int anim_no = rand_int(3) + ANIM_SCRAP_METAL;
1011                     object_create(scrap, gs, pos, vec2f_create(velx, vely));
1012                     object_set_animation(scrap, &af_get_move(h->af_data, anim_no)->ani);
1013                     object_set_gravity(scrap, 0.4f);
1014                     object_set_pal_offset(scrap, object_get_pal_offset(h_obj));
1015                     object_set_layers(scrap, LAYER_SCRAP);
1016                     object_set_shadow(scrap, 1);
1017                     object_dynamic_tick(scrap);
1018                     scrap_create(scrap);
1019                     game_state_add_object(gs, scrap, RENDER_LAYER_TOP, 0, 0);
1020                 }
1021             }
1022         }
1023     } // if(!paused)
1024 
1025     int need_sync = 0;
1026     // allow enemy HARs to move during a network game
1027     need_sync += arena_handle_events(scene, player1, player1->ctrl->extra_events);
1028     need_sync += arena_handle_events(scene, player2, player2->ctrl->extra_events);
1029     arena_maybe_sync(scene, need_sync);
1030 }
1031 
arena_static_tick(scene * scene,int paused)1032 void arena_static_tick(scene *scene, int paused) {
1033     arena_local *local = scene_get_userdata(scene);
1034     guiframe_tick(local->game_menu);
1035 }
1036 
arena_input_tick(scene * scene)1037 void arena_input_tick(scene *scene) {
1038     game_player *player1 = game_state_get_player(scene->gs, 0);
1039     game_player *player2 = game_state_get_player(scene->gs, 1);
1040 
1041     ctrl_event *p1 = NULL, *p2 = NULL;
1042     controller_poll(player1->ctrl, &p1);
1043     controller_poll(player2->ctrl, &p2);
1044 
1045     int need_sync = 0;
1046     need_sync += arena_handle_events(scene, player1, p1);
1047     need_sync += arena_handle_events(scene, player2, p2);
1048     controller_free_chain(p1);
1049     controller_free_chain(p2);
1050     arena_maybe_sync(scene, need_sync);
1051 }
1052 
arena_event(scene * scene,SDL_Event * e)1053 int arena_event(scene *scene, SDL_Event *e) {
1054     // ESC during demo mode jumps you back to the main menu
1055     if (e->type == SDL_KEYDOWN && is_demoplay(scene) && e->key.keysym.sym == SDLK_ESCAPE) {
1056         game_state_set_next(scene->gs, SCENE_MENU);
1057     }
1058     return 0;
1059 }
1060 
arena_render_overlay(scene * scene)1061 void arena_render_overlay(scene *scene) {
1062     arena_local *local = scene_get_userdata(scene);
1063 
1064     // Render bars
1065     game_player *player[2];
1066     object *obj[2];
1067 
1068     char buf[40];
1069 #ifdef DEBUGMODE
1070     sprintf(buf, "%u", game_state_get_tick(scene->gs));
1071     font_render(&font_small, buf, 160, 0, TEXT_COLOR);
1072     sprintf(buf, "%u", rand_get_seed());
1073     font_render(&font_small, buf, 130, 8, TEXT_COLOR);
1074 #endif
1075     for(int i = 0; i < 2; i++) {
1076         player[i] = game_state_get_player(scene->gs, i);
1077         obj[i] = game_player_get_har(player[i]);
1078     }
1079     if(obj[0] != NULL && obj[1] != NULL) {
1080         //  Render progress bar components
1081         for(int i = 0; i < 2; i++) {
1082             component_render(local->health_bars[i]);
1083             component_render(local->endurance_bars[i]);
1084         }
1085 
1086         // Render HAR and pilot names
1087         font_render_shadowed(&font_small,
1088                             lang_get(player[0]->pilot_id+20),
1089                             5, 19,
1090                             TEXT_COLOR,
1091                             TEXT_SHADOW_RIGHT|TEXT_SHADOW_BOTTOM);
1092         font_render_shadowed(&font_small,
1093                             lang_get((player[0]->har_id)+31),
1094                             5, 26,
1095                             TEXT_COLOR,
1096                             TEXT_SHADOW_RIGHT|TEXT_SHADOW_BOTTOM);
1097 
1098         int p2len = (strlen(lang_get(player[1]->pilot_id+20))-1) * font_small.w;
1099         int h2len = (strlen(lang_get((player[1]->har_id)+31))-1) * font_small.w;
1100         font_render_shadowed(&font_small,
1101                             lang_get(player[1]->pilot_id+20),
1102                             315-p2len, 19,
1103                             TEXT_COLOR,
1104                             TEXT_SHADOW_RIGHT|TEXT_SHADOW_BOTTOM);
1105         font_render_shadowed(&font_small,
1106                             lang_get((player[1]->har_id)+31),
1107                             315-h2len, 26,
1108                             TEXT_COLOR,
1109                             TEXT_SHADOW_RIGHT|TEXT_SHADOW_BOTTOM);
1110 
1111         // Render score stuff
1112         chr_score_render(game_player_get_score(player[0]));
1113 
1114         // Do not render player 2 score in 1 player mode
1115         if(game_player_get_selectable(player[1])) {
1116             chr_score_render(game_player_get_score(player[1]));
1117         }
1118 
1119         // render ping, if player is networked
1120         if (player[0]->ctrl->type == CTRL_TYPE_NETWORK) {
1121             sprintf(buf, "ping %u", player[0]->ctrl->rtt);
1122             font_render(&font_small, buf, 5, 40, TEXT_COLOR);
1123         }
1124         if (player[1]->ctrl->type == CTRL_TYPE_NETWORK) {
1125             sprintf(buf, "ping %u", player[1]->ctrl->rtt);
1126             font_render(&font_small, buf, 315-(strlen(buf)*font_small.w), 40, TEXT_COLOR);
1127         }
1128 
1129         for (int i = 0; i < 2; i++) {
1130             for (int j = 0; j < 4; j++) {
1131                 if (local->player_rounds[i][j]) {
1132                     object_render(local->player_rounds[i][j]);
1133                 }
1134             }
1135         }
1136     }
1137 
1138     // Render menu (if visible)
1139     if(local->menu_visible) {
1140         guiframe_render(local->game_menu);
1141         video_render_sprite(&local->sur, 10, 150, BLEND_ALPHA, 0);
1142     }
1143 }
1144 
arena_get_state(scene * scene)1145 int arena_get_state(scene *scene) {
1146     arena_local *local = scene_get_userdata(scene);
1147     return local->state;
1148 }
1149 
arena_set_state(scene * scene,int state)1150 void arena_set_state(scene *scene, int state) {
1151     arena_local *local = scene_get_userdata(scene);
1152     local->state = state;
1153 }
1154 
arena_toggle_rein(scene * scene)1155 void arena_toggle_rein(scene *scene) {
1156     arena_local *local = scene_get_userdata(scene);
1157     local->rein_enabled = !local->rein_enabled;
1158 }
1159 
arena_startup(scene * scene,int id,int * m_load,int * m_repeat)1160 void arena_startup(scene *scene, int id, int *m_load, int *m_repeat) {
1161     if(scene->bk_data.file_id == 64) {
1162         // Start up & repeat torches on arena startup
1163         switch(id) {
1164             case 1:
1165             case 2:
1166             case 3:
1167             case 4:
1168                 *m_load = 1;
1169                 *m_repeat = 1;
1170                 return;
1171         }
1172     }
1173 }
1174 
arena_create(scene * scene)1175 int arena_create(scene *scene) {
1176     settings *setting;
1177     arena_local *local;
1178 
1179     // Load up settings
1180     setting = settings_get();
1181 
1182     // Initialize Demo
1183     if(is_demoplay(scene)) {
1184         game_state_init_demo(scene->gs);
1185     }
1186 
1187     // Handle music playback
1188     switch(scene->bk_data.file_id) {
1189         case 8:   music_play(PSM_ARENA0); break;
1190         case 16:  music_play(PSM_ARENA1); break;
1191         case 32:  music_play(PSM_ARENA2); break;
1192         case 64:  music_play(PSM_ARENA3); break;
1193         case 128: music_play(PSM_ARENA4); break;
1194     }
1195 
1196     // Initialize local struct
1197     local = malloc(sizeof(arena_local));
1198     scene_set_userdata(scene, local);
1199 
1200     // Set correct state
1201     local->state = ARENA_STATE_STARTING;
1202     local->ending_ticks = 0;
1203     local->rein_enabled = 0;
1204 
1205     local->round = 0;
1206     switch (setting->gameplay.rounds) {
1207         case 0:
1208             local->rounds = 1;
1209             break;
1210         case 1:
1211             local->rounds = 3;
1212             break;
1213         case 2:
1214             local->rounds = 5;
1215             break;
1216         case 3:
1217             local->rounds = 7;
1218             break;
1219         default:
1220             local->rounds = 1;
1221             break;
1222     }
1223     local->over = 0;
1224 
1225     // Initial har data
1226     vec2i pos[2];
1227     int dir[2] = {OBJECT_FACE_RIGHT, OBJECT_FACE_LEFT};
1228     pos[0] = vec2i_create(HAR1_START_POS, ARENA_FLOOR);
1229     pos[1] = vec2i_create(HAR2_START_POS, ARENA_FLOOR);
1230 
1231     // init HARs
1232     for(int i = 0; i < 2; i++) {
1233         // Declare some vars
1234         game_player *player = game_state_get_player(scene->gs, i);
1235         object *obj = malloc(sizeof(object));
1236 
1237         // load the player's colors into the palette
1238         palette *base_pal = video_get_base_palette();
1239         palette_set_player_color(base_pal, i, player->colors[2], 0);
1240         palette_set_player_color(base_pal, i, player->colors[1], 1);
1241         palette_set_player_color(base_pal, i, player->colors[0], 2);
1242         video_force_pal_refresh();
1243 
1244         // Create object and specialize it as HAR.
1245         // Errors are unlikely here, but check anyway.
1246 
1247         if (scene_load_har(scene, i, player->har_id)) {
1248             free(obj);
1249             return 1;
1250         }
1251 
1252         object_create(obj, scene->gs, pos[i], vec2f_create(0,0));
1253         if(har_create(obj, scene->af_data[i], dir[i], player->har_id, player->pilot_id, i)) {
1254             return 1;
1255         }
1256 
1257         // Set HAR to controller and game_player
1258         game_state_add_object(scene->gs, obj, RENDER_LAYER_MIDDLE, 0, 0);
1259 
1260         // Set HAR for player
1261         game_player_set_har(player, obj);
1262         game_player_get_ctrl(player)->har = obj;
1263 
1264         // Create round tokens
1265         for (int j = 0; j < 4; j++) {
1266             if (j < ceil(local->rounds / 2.0f)) {
1267                 local->player_rounds[i][j] = malloc(sizeof(object));
1268                 int xoff = 110 + 9 * j + 3 + j;
1269                 if (i == 1) {
1270                     xoff = 210 - 9 * j - 3 - j;
1271                 }
1272                 animation *ani = &bk_get_info(&scene->bk_data, 27)->ani;
1273                 object_create(local->player_rounds[i][j], scene->gs, vec2i_create(xoff ,9), vec2f_create(0, 0));
1274                 object_set_animation(local->player_rounds[i][j], ani);
1275                 object_select_sprite(local->player_rounds[i][j], 1);
1276             } else {
1277                 local->player_rounds[i][j] = NULL;
1278             }
1279         }
1280     }
1281 
1282     // remove the keyboard hooks
1283 
1284     game_player *_player[2];
1285     for(int i = 0; i < 2; i++) {
1286         _player[i] = game_state_get_player(scene->gs, i);
1287     }
1288     if(game_player_get_ctrl(_player[0])->type == CTRL_TYPE_NETWORK) {
1289         controller_clear_hooks(game_player_get_ctrl(_player[1]));
1290     }
1291     if(game_player_get_ctrl(_player[1])->type == CTRL_TYPE_NETWORK) {
1292         controller_clear_hooks(game_player_get_ctrl(_player[0]));
1293     }
1294 
1295     controller_set_repeat(game_player_get_ctrl(_player[0]), 1);
1296     controller_set_repeat(game_player_get_ctrl(_player[1]), 1);
1297 
1298     game_player_get_har(_player[0])->animation_state.enemy = game_player_get_har(_player[1]);
1299     game_player_get_har(_player[1])->animation_state.enemy = game_player_get_har(_player[0]);
1300 
1301     maybe_install_har_hooks(scene);
1302 
1303     // Arena menu text settings
1304     text_settings tconf;
1305     text_defaults(&tconf);
1306     tconf.font = FONT_BIG;
1307     tconf.cforeground = color_create(0, 121, 0, 255);
1308     tconf.halign = TEXT_CENTER;
1309 
1310     // Arena menu
1311     local->menu_visible = 0;
1312     local->game_menu = guiframe_create(60, 5, 181, 117);
1313     component *menu = menu_create(11);
1314     menu_attach(menu, label_create(&tconf, "OPENOMF"));
1315     menu_attach(menu, filler_create());
1316     menu_attach(menu, filler_create());
1317     component *return_button = textbutton_create(&tconf, "RETURN TO GAME", COM_ENABLED, game_menu_return, scene);
1318     menu_attach(menu, return_button);
1319 
1320     menu_attach(menu, textslider_create_bind(&tconf, "SOUND", 10, 1, arena_sound_slide, NULL, &setting->sound.sound_vol));
1321     menu_attach(menu, textslider_create_bind(&tconf, "MUSIC", 10, 1, arena_music_slide, NULL, &setting->sound.music_vol));
1322 
1323     component *speed_slider = textslider_create_bind(&tconf, "SPEED", 10, 0, arena_speed_slide, scene, &setting->gameplay.speed);
1324     if(is_netplay(scene)) {
1325         component_disable(speed_slider, 1);
1326     }
1327     menu_attach(menu, speed_slider);
1328 
1329     menu_attach(menu, textbutton_create(&tconf, "VIDEO OPTIONS", COM_DISABLED, NULL, NULL));
1330     menu_attach(menu, textbutton_create(&tconf, "HELP", COM_DISABLED, NULL, NULL));
1331     menu_attach(menu, textbutton_create(&tconf, "QUIT", COM_ENABLED, game_menu_quit, scene));
1332 
1333     guiframe_set_root(local->game_menu, menu);
1334     guiframe_layout(local->game_menu);
1335     menu_select(menu, return_button);
1336 
1337     // background for the 'help' at the bottom of the screen
1338     // TODO support rendering text onto it
1339     menu_background_create(&local->sur, 301, 37);
1340 
1341     // Health and endurance bars
1342     local->health_bars[0] = progressbar_create(PROGRESSBAR_THEME_HEALTH, PROGRESSBAR_RIGHT, 100);
1343     component_layout(local->health_bars[0], 5, 5, 100, 8);
1344     local->health_bars[1] = progressbar_create(PROGRESSBAR_THEME_HEALTH, PROGRESSBAR_LEFT, 100);
1345     component_layout(local->health_bars[1], 215, 5, 100, 8);
1346     local->endurance_bars[0] = progressbar_create(PROGRESSBAR_THEME_ENDURANCE, PROGRESSBAR_RIGHT, 100);
1347     component_layout(local->endurance_bars[0], 5, 14, 100, 4);
1348     local->endurance_bars[1] = progressbar_create(PROGRESSBAR_THEME_ENDURANCE, PROGRESSBAR_LEFT, 100);
1349     component_layout(local->endurance_bars[1], 215, 14, 100, 4);
1350 
1351     // Score positioning
1352     chr_score_set_pos(game_player_get_score(_player[0]), 5, 33, OBJECT_FACE_RIGHT);
1353     chr_score_set_pos(game_player_get_score(_player[1]), 315, 33, OBJECT_FACE_LEFT); // TODO: Set better coordinates for this
1354 
1355     // Reset the score
1356     chr_score_reset(game_player_get_score(_player[0]), !is_singleplayer(scene));
1357     chr_score_reset(game_player_get_score(_player[1]), 1);
1358 
1359     // Reset the win counter in single player mode
1360     if(is_singleplayer(scene)) {
1361         chr_score_reset_wins(game_player_get_score(_player[0]));
1362         chr_score_reset_wins(game_player_get_score(_player[1]));
1363     }
1364 
1365     // Reset screencaps
1366     har_screencaps_reset(&_player[0]->screencaps);
1367     har_screencaps_reset(&_player[1]->screencaps);
1368 
1369     // Set correct sounds for ready, round and number STL fields
1370     scene->bk_data.sound_translation_table[14] = 10; // READY
1371     scene->bk_data.sound_translation_table[15] = 16; // ROUND
1372     scene->bk_data.sound_translation_table[3] = 23 + local->round; // NUMBER
1373 
1374     // Disable the floating ball disappearence sound in fire arena
1375     if(scene->id == SCENE_ARENA3) {
1376         scene->bk_data.sound_translation_table[20] = 0;
1377     }
1378 
1379     if (local->rounds == 1) {
1380         // Start READY animation
1381         animation *ready_ani = &bk_get_info(&scene->bk_data, 11)->ani;
1382         object *ready = malloc(sizeof(object));
1383         object_create(ready, scene->gs, ready_ani->start_pos, vec2f_create(0,0));
1384         object_set_stl(ready, scene->bk_data.sound_translation_table);
1385         object_set_animation(ready, ready_ani);
1386         object_set_finish_cb(ready, scene_ready_anim_done);
1387         game_state_add_object(scene->gs, ready, RENDER_LAYER_TOP, 0, 0);
1388     } else {
1389         // ROUND
1390         animation *round_ani = &bk_get_info(&scene->bk_data, 6)->ani;
1391         object *round = malloc(sizeof(object));
1392         object_create(round, scene->gs, round_ani->start_pos, vec2f_create(0,0));
1393         object_set_stl(round, scene->bk_data.sound_translation_table);
1394         object_set_animation(round, round_ani);
1395         object_set_finish_cb(round, scene_ready_anim_done);
1396         game_state_add_object(scene->gs, round, RENDER_LAYER_TOP, 0, 0);
1397 
1398         // Number
1399         animation *number_ani = &bk_get_info(&scene->bk_data, 7)->ani;
1400         object *number = malloc(sizeof(object));
1401         object_create(number, scene->gs, number_ani->start_pos, vec2f_create(0,0));
1402         object_set_stl(number, scene->bk_data.sound_translation_table);
1403         object_set_animation(number, number_ani);
1404         object_select_sprite(number, local->round);
1405         game_state_add_object(scene->gs, number, RENDER_LAYER_TOP, 0, 0);
1406     }
1407 
1408     // Callbacks
1409     scene_set_event_cb(scene, arena_event);
1410     scene_set_free_cb(scene, arena_free);
1411     scene_set_dynamic_tick_cb(scene, arena_dynamic_tick);
1412     scene_set_static_tick_cb(scene, arena_static_tick);
1413     scene_set_startup_cb(scene, arena_startup);
1414     scene_set_input_poll_cb(scene, arena_input_tick);
1415     scene_set_render_overlay_cb(scene, arena_render_overlay);
1416 
1417     // Pick renderer
1418     video_select_renderer(VIDEO_RENDERER_HW);
1419 
1420     // initalize recording, if enabled
1421     if (scene->gs->init_flags->record == 1) {
1422         local->rec = malloc(sizeof(sd_rec_file));
1423         sd_rec_create(local->rec);
1424         for(int i = 0; i < 2; i++) {
1425             // Declare some vars
1426             game_player *player = game_state_get_player(scene->gs, i);
1427             DEBUG("player %d using har %d", i, player->har_id);
1428             local->rec->pilots[i].info.har_id = (unsigned char)player->har_id;
1429             local->rec->pilots[i].info.pilot_id = player->pilot_id;
1430             local->rec->pilots[i].info.color_1 = player->colors[2];
1431             local->rec->pilots[i].info.color_2 = player->colors[1];
1432             local->rec->pilots[i].info.color_3 = player->colors[0];
1433             memcpy(local->rec->pilots[i].info.name, lang_get(player->pilot_id+20), 18);
1434         }
1435     } else{
1436         local->rec = NULL;
1437     }
1438 
1439     // All done!
1440     return 0;
1441 }
1442