Major refactoring. Hitboxes.
[hackover2013-badge-firmware.git] / badge / jumpnrun / jumpnrun.c
1 #include "jumpnrun.h"
2 #include "collision.h"
3 #include "levels.h"
4
5 #include "../ui/display.h"
6 #include "../ui/event.h"
7 #include "../ui/sprite.h"
8 #include "../util/util.h"
9
10 #include <assert.h>
11 #include <math.h>
12 #include <stddef.h>
13 #include <stdio.h>
14
15 static vec2d const gravity = { FIXED_POINT_I(0, 0), FIXED_POINT_I(0, 56) };
16 static vec2d const move_max = { FIXED_POINT_I(0, 600), FIXED_POINT_I(1, 300) };
17 static fixed_point const accel_horiz = FIXED_POINT_I(0, 50);
18 static fixed_point const accel_vert = FIXED_POINT_I(0, 167);
19 static fixed_point const drag_factor = FIXED_POINT_I(0, 854);
20 static fixed_point const speed_jump_x = FIXED_POINT_I(0, 600);
21
22 static vec2d const hacker_extent = { FIXED_INT_I(5), FIXED_INT_I(8) };
23
24 static badge_sprite const anim_hacker[] = {
25 { 5, 8, (uint8_t const *) "\x1c\xff\xfd\x04\x04" },
26 { 5, 8, (uint8_t const *) "\x1c\xff\x3d\xc4\x04" },
27 { 5, 8, (uint8_t const *) "\xdc\x3f\x1d\x24\xc4" },
28 { 5, 8, (uint8_t const *) "\x1c\xff\x3d\xc4\x04" }
29
30 /*
31 { 5, 8, (uint8_t const *) "\x46\xfc\x73\x8c\x31" },
32 { 5, 8, (uint8_t const *) "\x46\xfc\x73\x8c\x52" },
33 { 5, 8, (uint8_t const *) "\x46\xfc\x73\x94\x8c" },
34 { 5, 8, (uint8_t const *) "\x46\xfc\x73\x8c\x52" }
35 */
36 /*
37 { 6, 8, (uint8_t const *) "\x0c\xe1\x3b\x0e\xc3\x30" },
38 { 6, 8, (uint8_t const *) "\x0c\xe1\x3b\x0e\x43\x51" },
39 { 6, 8, (uint8_t const *) "\x0c\xe1\x3b\x0e\x35\x82" },
40 { 6, 8, (uint8_t const *) "\x0c\xe1\x3b\x0e\x43\x51" }
41 */
42 /*
43 { 6, 8, (uint8_t const *) "\xff\xff\xff\xff\xff\xff" },
44 { 6, 8, (uint8_t const *) "\xff\xff\xff\xff\xff\xff" },
45 { 6, 8, (uint8_t const *) "\xff\xff\xff\xff\xff\xff" },
46 { 6, 8, (uint8_t const *) "\xff\xff\xff\xff\xff\xff" }
47 */
48 };
49
50
51 static inline int imax(int x, int y) {
52 return x < y ? y : x;
53 }
54
55 static inline fixed_point hacker_left (vec2d const *pos, jumpnrun_game_state const *state) { (void) state; return pos->x; }
56 static inline fixed_point hacker_top (vec2d const *pos, jumpnrun_game_state const *state) { (void) state; return pos->y; }
57 static inline fixed_point hacker_right (vec2d const *pos, jumpnrun_game_state const *state) { return fixed_point_add(hacker_left(pos, state), hacker_extent.x); }
58 static inline fixed_point hacker_bottom(vec2d const *pos, jumpnrun_game_state const *state) { return fixed_point_add(hacker_top (pos, state), hacker_extent.y); }
59
60 int jumpnrun_level_assert_left_side(jumpnrun_game_state const *state) {
61 static int const lmargin = 20;
62 static int const rmargin = 50;
63
64 int pos_cur = fixed_point_cast_int(state->player.current_box.pos.x);
65 int pos_rel = pos_cur - state->left;
66
67 if(pos_rel < lmargin) {
68 return imax(0, pos_cur - lmargin);
69 } else if(pos_rel > BADGE_DISPLAY_WIDTH - rmargin) {
70 return pos_cur - (BADGE_DISPLAY_WIDTH - rmargin);
71 }
72
73 return state->left;
74 }
75
76 static int jumpnrun_bsearch_tile(jumpnrun_level const *lv, jumpnrun_game_state const *state) {
77 int front = 0;
78 int len = lv->header.tile_count;
79
80 while(len > 0) {
81 int mid = front + len / 2;
82
83 if(fixed_point_lt(tile_right(&lv->tiles[mid]), FIXED_INT(state->left - JUMPNRUN_MAX_SPAWN_MARGIN))) {
84 front = mid + 1;
85 len -= len / 2 + 1;
86 } else {
87 len /= 2;
88 }
89 }
90
91 return front;
92 }
93
94 jumpnrun_tile_range jumpnrun_visible_tiles(jumpnrun_level const *lv,
95 jumpnrun_game_state const *state) {
96 jumpnrun_tile_range r;
97
98 r.first = jumpnrun_bsearch_tile(lv, state);
99
100 for(r.last = r.first;
101 r.last < lv->header.tile_count && lv->tiles[r.last].pos.x * JUMPNRUN_TILE_PIXEL_WIDTH < state->left + BADGE_DISPLAY_WIDTH + JUMPNRUN_MAX_SPAWN_MARGIN;
102 ++r.last)
103 ;
104
105 return r;
106 }
107
108 void jumpnrun_passive_movement(vec2d *inertia)
109 {
110 *inertia = vec2d_add(*inertia, gravity);
111
112 inertia->x = fixed_point_min(fixed_point_max(fixed_point_neg(move_max.x), inertia->x), move_max.x);
113 inertia->y = fixed_point_min(fixed_point_max(fixed_point_neg(move_max.y), inertia->y), move_max.y);
114 }
115
116 static void jumpnrun_apply_movement(jumpnrun_level const *lv,
117 jumpnrun_tile_range const *tilerange,
118 jumpnrun_game_state *state,
119 vec2d *inertia_mod) {
120 switch(badge_event_current_input_state() &
121 (BADGE_EVENT_KEY_LEFT |
122 BADGE_EVENT_KEY_RIGHT)) {
123 case BADGE_EVENT_KEY_LEFT:
124 // state->player.inertia.x = state->player.touching_ground ? fixed_point_sub(state->player.inertia.x, accel_horiz) : fixed_point_neg(speed_jump_x);
125 state->player.inertia.x = fixed_point_sub(state->player.inertia.x, accel_horiz);
126 state->anim_direction = BADGE_BLT_MIRRORED;
127 break;
128 case BADGE_EVENT_KEY_RIGHT:
129 // state->player.inertia.x = state->player.touching_ground ? fixed_point_add(state->player.inertia.x, accel_horiz) : speed_jump_x;
130 state->player.inertia.x = fixed_point_add(state->player.inertia.x, accel_horiz);
131 state->anim_direction = 0;
132 break;
133 default:
134 if(state->player.touching_ground) {
135 state->player.inertia.x = fixed_point_mul(state->player.inertia.x, drag_factor);
136 } //else {
137 //state->player.inertia.x = FIXED_INT(0);
138 //}
139
140 break;
141 }
142
143 if(state->player.jumpable_frames == 0) {
144 // intentionally left blank.
145 } else if(badge_event_current_input_state() & BADGE_EVENT_KEY_BTN_A) {
146 state->player.inertia.y = fixed_point_sub(state->player.inertia.y, accel_vert);
147 --state->player.jumpable_frames;
148 } else {
149 state->player.jumpable_frames = 0;
150 }
151
152 jumpnrun_passive_movement(&state->player.inertia);
153
154 vec2d new_pos = vec2d_add(state->player.current_box.pos, state->player.inertia);
155
156 if(fixed_point_lt(new_pos.x, FIXED_INT(state->left))) {
157 new_pos.x = FIXED_INT(state->left);
158 state->player.inertia.x = FIXED_INT(0);
159 }
160
161 *inertia_mod = state->player.inertia;
162 collisions_tiles_displace(&new_pos, &state->player, lv, tilerange, inertia_mod);
163 state->player.inertia = *inertia_mod;
164
165 if(fixed_point_gt(state->player.current_box.pos.y, FIXED_INT(BADGE_DISPLAY_HEIGHT))) {
166 state->status = JUMPNRUN_DEAD;
167 }
168 }
169
170 void jumpnrun_level_tick(jumpnrun_level *lv,
171 jumpnrun_game_state *state)
172 {
173 jumpnrun_tile_range tilerange = jumpnrun_visible_tiles(lv, state);
174 vec2d inertia_mod = state->player.inertia;
175
176 jumpnrun_apply_movement(lv, &tilerange, state, &inertia_mod);
177 state->left = jumpnrun_level_assert_left_side(state);
178
179 if(state->player.tick_minor == 0) {
180 badge_framebuffer fb;
181 badge_framebuffer_clear(&fb);
182
183 for(size_t tile = tilerange.first; tile < tilerange.last; ++tile) {
184 badge_framebuffer_blt(&fb,
185 fixed_point_cast_int(tile_left(&lv->tiles[tile])) - state->left,
186 fixed_point_cast_int(tile_top (&lv->tiles[tile])),
187 &tile_type(&lv->tiles[tile])->sprite,
188 0);
189 }
190
191 for(size_t item = 0; item < lv->header.item_count; ++item) {
192 int screenpos = fixed_point_cast_int(lv->items[item].pos.x) - state->left;
193 if(screenpos > -lv->items[item].type->sprite.width &&
194 screenpos < BADGE_DISPLAY_WIDTH) {
195 rectangle item_rect = rect_from_item(&lv->items[item]);
196
197 if(rectangle_intersect(&state->player.current_box, &item_rect)) {
198 lv->items[item].type->on_collect(state);
199 }
200
201 badge_framebuffer_blt(&fb,
202 screenpos,
203 fixed_point_cast_int(lv->items[item].pos.y),
204 &lv->items[item].type->sprite,
205 0);
206 }
207 }
208
209 for(size_t enemy_ix = 0; enemy_ix < lv->header.enemy_count; ++enemy_ix) {
210 jumpnrun_enemy *enemy = &lv->enemies[enemy_ix];
211 jumpnrun_process_enemy(enemy, &fb, state, lv, &tilerange, &inertia_mod);
212 }
213
214 badge_framebuffer_blt(&fb,
215 fixed_point_cast_int(state->player.current_box.pos.x) - state->left,
216 fixed_point_cast_int(state->player.current_box.pos.y),
217 &anim_hacker[state->player.anim_frame],
218 state->anim_direction);
219
220 badge_framebuffer_flush(&fb);
221
222 if(!state->player.touching_ground) {
223 state->player.anim_frame = 2;
224 } else if(fixed_point_gt(fixed_point_abs(state->player.inertia.x), FIXED_POINT(0, 200))) {
225 state->player.anim_frame = (state->player.anim_frame + 1) % ARRAY_SIZE(anim_hacker);
226 } else {
227 state->player.anim_frame = 0;
228 }
229 } else {
230 for(size_t enemy_ix = 0; enemy_ix < lv->header.enemy_count; ++enemy_ix) {
231 jumpnrun_enemy *enemy = &lv->enemies[enemy_ix];
232 jumpnrun_process_enemy(enemy, NULL, state, lv, &tilerange, &inertia_mod);
233 }
234 }
235
236 state->player.inertia = inertia_mod;
237 ++state->player.tick_minor;
238 if(state->player.tick_minor == 3) {
239 state->player.tick_minor = 0;
240 }
241 }
242
243 uint8_t jumpnrun_play(char const *lvname) {
244 jumpnrun_level lv;
245
246 JUMPNRUN_LEVEL_LOAD(lv, lvname);
247
248 jumpnrun_game_state gs;
249 memset(&gs, 0, sizeof(gs));
250
251 gs.player.current_box = rectangle_new(lv.start_pos, hacker_extent);
252
253 while(gs.status == JUMPNRUN_PLAYING) {
254 badge_event_t ev = badge_event_wait();
255
256 switch(badge_event_type(ev)) {
257 case BADGE_EVENT_USER_INPUT:
258 {
259 uint8_t old_state = badge_event_old_input_state(ev);
260 uint8_t new_state = badge_event_new_input_state(ev);
261 uint8_t new_buttons = new_state & (old_state ^ new_state);
262
263 if((new_buttons & BADGE_EVENT_KEY_BTN_A) && gs.player.touching_ground) {
264 gs.player.jumpable_frames = 12;
265 }
266
267 break;
268 }
269 case BADGE_EVENT_GAME_TICK:
270 {
271 jumpnrun_level_tick(&lv, &gs);
272 break;
273 }
274 }
275 }
276
277 return gs.status;
278 }
This page took 0.055287 seconds and 5 git commands to generate.