270 asc_texture_bind(TEXTURE_PLAYER_COLOR_MAP, shader->map_color, 1); |
271 asc_texture_bind(TEXTURE_PLAYER_COLOR_MAP, shader->map_color, 1); |
271 asc_shader_upload_color(shader->color, player->color); |
272 asc_shader_upload_color(shader->color, player->color); |
272 asc_mesh_draw_triangle_strip(&sprite->mesh); |
273 asc_mesh_draw_triangle_strip(&sprite->mesh); |
273 } |
274 } |
274 |
275 |
275 static void player_set_health(Player *player, unsigned health) { |
276 static void player_position(Player *pl, unsigned x, unsigned y) { |
276 player->health = health; |
277 pl->new_position.x = x; |
277 // TODO: probably we want to add more effects when the health changes |
278 pl->new_position.y = y; |
278 } |
279 pl->reset_position = true; |
279 |
280 } |
280 static unsigned player_get_health(Player *player) { |
281 |
281 return player->health; |
282 static void player_position_random(Player *pl) { |
|
283 // TODO: check if the spawn location is viable when there is more action on the board |
|
284 player_position( |
|
285 pl, |
|
286 4u + asc_util_rand(GAME_FIELD_SIZE - 8u), |
|
287 4u + asc_util_rand(GAME_FIELD_SIZE - 8u) |
|
288 ); |
|
289 } |
|
290 |
|
291 static void player_main_behavior(AscBehavior *behavior) { |
|
292 AscSceneNode *node = behavior->node; |
|
293 Player *player = node->user_data; |
|
294 |
|
295 if (player->alive && player->health == 0) { |
|
296 player->alive = false; |
|
297 asc_scene_node_hide(node); |
|
298 // TODO: probably we don't want the entire trace to disappear instantly |
|
299 cxListClear(player->trace); |
|
300 // TODO: this should be controlled by another behavior that watches all players |
|
301 game.state = GAME_STATE_GAME_OVER; |
|
302 } |
|
303 |
|
304 // TODO: replace with respawn event |
|
305 if (!player->alive && player->health > 0) { |
|
306 player_position_random(player); |
|
307 player->alive = true; |
|
308 asc_scene_node_show(node); |
|
309 } |
282 } |
310 } |
283 |
311 |
284 static void player_move(AscBehavior *behavior) { |
312 static void player_move(AscBehavior *behavior) { |
285 AscSceneNode *node = behavior->node; |
313 AscSceneNode *node = behavior->node; |
286 Player *player = node->user_data; |
314 Player *player = node->user_data; |
287 |
|
288 // TODO: instead of skipping this behavior, it should be disabled when health is zero |
|
289 if (player_get_health(player) == 0) return; |
|
290 |
|
291 // TODO: move this to a different behavior |
|
292 asc_scene_node_show(node); |
|
293 |
315 |
294 const float ts = (float) GAME_FIELD_TILE_SIZE; |
316 const float ts = (float) GAME_FIELD_TILE_SIZE; |
295 |
317 |
296 // check if the position is set programmatically |
318 // check if the position is set programmatically |
297 if (player->reset_position) { |
319 if (player->reset_position) { |
352 |
374 |
353 // die when leaving the game field |
375 // die when leaving the game field |
354 if (node->position.x < 0 || node->position.y < 0 || |
376 if (node->position.x < 0 || node->position.y < 0 || |
355 node->position.x > GAME_FIELD_SIZE*GAME_FIELD_TILE_SIZE || |
377 node->position.x > GAME_FIELD_SIZE*GAME_FIELD_TILE_SIZE || |
356 node->position.y > GAME_FIELD_SIZE*GAME_FIELD_TILE_SIZE) { |
378 node->position.y > GAME_FIELD_SIZE*GAME_FIELD_TILE_SIZE) { |
357 // TODO: add fancy death animation |
379 // TODO: replace setting health to zero with a "kill" event |
358 asc_scene_node_hide(node); |
380 player->health = 0; |
359 player_set_health(player, 0); |
|
360 // TODO: remove the trace gradually (dequeuing the trace should be a different behavior) |
|
361 cxListClear(player->trace); |
|
362 game.state = GAME_STATE_GAME_OVER; |
|
363 return; |
381 return; |
364 } |
382 } |
365 |
383 |
366 // TODO: collision detection |
384 // TODO: collision detection |
367 |
385 |
381 } |
399 } |
382 } |
400 } |
383 } |
401 } |
384 |
402 |
385 static void player_controls(AscBehavior *behavior) { |
403 static void player_controls(AscBehavior *behavior) { |
386 // TODO: instead of checking the game state, disable the behavior when the player is dead |
|
387 if (game.state != GAME_STATE_PLAYING) return; |
|
388 |
|
389 Player *player = behavior->node->user_data; |
404 Player *player = behavior->node->user_data; |
390 // TODO: different key bindings for different player number in hot seat mode |
405 // TODO: different key bindings for different player number in hot seat mode |
391 if (asc_key_pressed(ASC_KEY(LEFT))) { |
406 if (asc_key_pressed(ASC_KEY(LEFT))) { |
392 if (player->direction != MOVE_RIGHT) { |
407 if (player->direction != MOVE_RIGHT) { |
393 player->target_direction = MOVE_LEFT; |
408 player->target_direction = MOVE_LEFT; |
406 if (asc_key_pressed(ASC_KEY(DOWN))) { |
421 if (asc_key_pressed(ASC_KEY(DOWN))) { |
407 if (player->direction != MOVE_UP) { |
422 if (player->direction != MOVE_UP) { |
408 player->target_direction = MOVE_DOWN; |
423 player->target_direction = MOVE_DOWN; |
409 } |
424 } |
410 } |
425 } |
411 } |
|
412 |
|
413 static void player_position(Player *pl, int x, int y) { |
|
414 pl->new_position.x = x; |
|
415 pl->new_position.y = y; |
|
416 pl->reset_position = true; |
|
417 } |
|
418 |
|
419 static void player_position_random(Player *pl) { |
|
420 // TODO: check if the spawn location is viable when there is more action on the board |
|
421 player_position( |
|
422 pl, |
|
423 4 + asc_util_rand(GAME_FIELD_SIZE - 8), |
|
424 4 + asc_util_rand(GAME_FIELD_SIZE - 8) |
|
425 ); |
|
426 } |
426 } |
427 |
427 |
428 static void player_destroy(CxAllocator *allocator, Player *player) { |
428 static void player_destroy(CxAllocator *allocator, Player *player) { |
429 cxListFree(player->trace); |
429 cxListFree(player->trace); |
430 cxFree(allocator, player); |
430 cxFree(allocator, player); |
442 .origin_x = GAME_FIELD_TILE_SIZE / 2, |
442 .origin_x = GAME_FIELD_TILE_SIZE / 2, |
443 .origin_y = GAME_FIELD_TILE_SIZE / 2, |
443 .origin_y = GAME_FIELD_TILE_SIZE / 2, |
444 ); |
444 ); |
445 asc_scene_add_node(MAIN_SCENE, node); |
445 asc_scene_add_node(MAIN_SCENE, node); |
446 Player *player = asc_scene_node_allocate_data(node, sizeof(Player)); |
446 Player *player = asc_scene_node_allocate_data(node, sizeof(Player)); |
447 player_position_random(player); |
|
448 player->speed = 3.f; // start with 3 tiles/sec |
447 player->speed = 3.f; // start with 3 tiles/sec |
449 player->number = 1; |
448 player->number = 1; |
450 player->health = 100; |
449 player->health = 100; |
451 player->color = ASC_RGB(1, 0, 0); |
450 player->color = ASC_RGB(1, 0, 0); |
452 player->trace = cxLinkedListCreateSimple(sizeof(asc_vec2u)); |
451 player->trace = cxLinkedListCreateSimple(sizeof(asc_vec2u)); |
453 cxDefineDestructor(player->trace, player_trace_release_tile); |
452 cxDefineDestructor(player->trace, player_trace_release_tile); |
454 node->draw_func = player_draw; |
453 node->draw_func = player_draw; |
455 node->user_data_free_func = (cx_destructor_func2)player_destroy; |
454 node->user_data_free_func = (cx_destructor_func2)player_destroy; |
456 // add behaviors (the order is important!) |
455 |
|
456 // add behaviors |
|
457 asc_behavior_add(node, player_main_behavior, .always_enabled = true); |
457 asc_behavior_add(node, player_controls); |
458 asc_behavior_add(node, player_controls); |
458 asc_behavior_add(node, player_move); |
459 asc_behavior_add(node, player_move); |
|
460 asc_behavior_pause_all_while_hidden(node); |
|
461 |
459 return player; |
462 return player; |
460 } |
463 } |
461 |
464 |
462 static asc_rect main_scene_viewport_update(asc_vec2u window_size) { |
465 static asc_rect main_scene_viewport_update(asc_vec2u window_size) { |
463 |
466 |
578 // game states |
581 // game states |
579 // TODO: move this into a behavior - probably add a state machine that enables/disables behaviors |
582 // TODO: move this into a behavior - probably add a state machine that enables/disables behaviors |
580 if (game.state == GAME_STATE_GAME_OVER) { |
583 if (game.state == GAME_STATE_GAME_OVER) { |
581 asc_scene_node_show(text_game_over); |
584 asc_scene_node_show(text_game_over); |
582 if (asc_key_pressed(ASC_KEY(R))) { |
585 if (asc_key_pressed(ASC_KEY(R))) { |
|
586 // TODO: instead of setting the health, send a "respawn" event to the behavior |
|
587 game.players[0]->health = 100; |
583 // TODO: re-load the "level" |
588 // TODO: re-load the "level" |
584 player_position_random(game.players[0]); |
|
585 player_set_health(game.players[0], 100); |
|
586 game.state = GAME_STATE_PLAYING; |
589 game.state = GAME_STATE_PLAYING; |
587 asc_scene_node_hide(text_game_over); |
590 asc_scene_node_hide(text_game_over); |
588 } |
591 } |
589 } |
592 } |
590 |
593 |