view src/map_state.c @ 47:f053a9f38c0e

core: implement basic scrolling, closes #2459
author David Demelier <markand@malikania.fr>
date Thu, 16 Jan 2020 13:32:20 +0100
parents
children 402aa7dcffe1
line wrap: on
line source

/*
 * map_state.c -- state when player is on a map
 *
 * Copyright (c) 2020 David Demelier <markand@malikania.fr>
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <stdio.h>

#include "event.h"
#include "game.h"
#include "map.h"
#include "map_state.h"
#include "painter.h"
#include "state.h"
#include "texture.h"
#include "walksprite.h"
#include "window.h"

/*
 * This is the speed the player moves on the map.
 *
 * SPEED represents the number of pixels it must move per SEC.
 * SEC simply represends the number of milliseconds in one second.
 */
#define SPEED 100
#define SEC   1000

/*
 * Those are margins within the edge of the screen. The camera always try to
 * keep those padding between the player and the screen.
 */
#define MARGIN_WIDTH    80
#define MARGIN_HEIGHT   80

/*
 * This structure defines the possible movement of the player as flags since
 * it's possible to make diagonal movements.
 */
enum movement {
	MOVING_UP       = 1 << 0,
	MOVING_RIGHT    = 1 << 1,
	MOVING_DOWN     = 1 << 2,
	MOVING_LEFT     = 1 << 3
};

/*
 * A bit of explanation within this array. The structure walksprite requires
 * an orientation between 0-7 depending on the user direction.
 *
 * Since keys for moving the character may be pressed at the same time, we need
 * a conversion table from "key pressed" to "orientation".
 *
 * When an orientation is impossible, it is set to -1. Example, when both left
 * and right are pressed.
 *
 * MOVING_UP    = 0001 = 0x1
 * MOVING_RIGHT = 0010 = 0x2
 * MOVING_DOWN  = 0100 = 0x3
 * MOVING_LEFT  = 1000 = 0x4
 */
static unsigned int orientations[16] = {
	[0x1] = 0,
	[0x2] = 2,
	[0x3] = 1,
	[0x4] = 4,
	[0x6] = 3,
	[0x8] = 6,
	[0x9] = 7,
	[0xC] = 5
};

static struct {
	struct map map;
	struct walksprite player_sprite;
	int player_x;
	int player_y;
	int player_orientation;
	int view_x;
	int view_y;
	enum movement moving;
} data;

static void
enter(void)
{
}

static void
leave(void)
{
}

static void
handle_keydown(const union event *event)
{
	switch (event->key.key) {
	case KEY_UP:
		data.moving |= MOVING_UP;
		break;
	case KEY_RIGHT:
		data.moving |= MOVING_RIGHT;
		break;
	case KEY_DOWN:
		data.moving |= MOVING_DOWN;
		break;
	case KEY_LEFT:
		data.moving |= MOVING_LEFT;
		break;
	default:
		break;
	}

	data.player_orientation = orientations[data.moving];
}

static void
handle_keyup(const union event *event)
{
	switch (event->key.key) {
	case KEY_UP:
		data.moving &= ~(MOVING_UP);
		break;
	case KEY_RIGHT:
		data.moving &= ~(MOVING_RIGHT);
		break;
	case KEY_DOWN:
		data.moving &= ~(MOVING_DOWN);
		break;
	case KEY_LEFT:
		data.moving &= ~(MOVING_LEFT);
		break;
	default:
		break;
	}
}

static void
move(unsigned int ticks)
{
	/* This is the amount of pixels the player must move. */
	const int delta = SPEED * ticks / SEC;

	/* This is the view width which simply equals to the screen. */
	const unsigned int view_w = window_width();
	const unsigned int view_h = window_height();

	/* This is the rectangle within the view where users must be. */
	const int margin_x = data.view_x + MARGIN_WIDTH;
	const int margin_y = data.view_y + MARGIN_HEIGHT;
	const unsigned int margin_w = window_width() - (MARGIN_WIDTH * 2);
	const unsigned int margin_h = window_height() - (MARGIN_HEIGHT * 2);

	/* This is map size. */
	const unsigned int map_w = texture_width(data.map.picture);
	const unsigned int map_h = texture_height(data.map.picture);

	int dx = 0;
	int dy = 0;

	if (data.moving == 0)
		return;

	if (data.moving & MOVING_UP)
		dy = -1;
	if (data.moving & MOVING_DOWN)
		dy = 1;
	if (data.moving & MOVING_LEFT)
		dx = -1;
	if (data.moving & MOVING_RIGHT)
		dx = 1;

	/* Move the player and adjust view if needed. */
	if (dx > 0) {
		data.player_x += delta;

		if (data.player_x > (int)(margin_x + margin_w)) {
			data.view_x = (data.player_x - view_w) + MARGIN_WIDTH;

			if (data.view_x >= (int)(map_w - view_w))
				data.view_x = map_w - view_w;
		}

		if (data.player_x > map_w - 48)
			data.player_x = map_w - 48;
	} else if (dx < 0) {
		data.player_x -= delta;

		if (data.player_x < margin_x) {
			data.view_x = data.player_x - MARGIN_WIDTH;

			if (data.view_x < 0)
				data.view_x = 0;
		}

		if (data.player_x < 0)
			data.player_x = 0;
	}

	if (dy > 0) {
		data.player_y += delta;

		if (data.player_y > (int)(margin_y + margin_h)) {
			data.view_y = (data.player_y - view_h) + MARGIN_HEIGHT;

			if (data.view_y >= (int)(map_h - view_h))
				data.view_y = map_h - view_h;
		}

		if (data.player_y > map_h - 48)
			data.player_y = map_h - 48;
	} else if (dy < 0) {
		data.player_y -= delta;

		if (data.player_y < margin_y) {
			data.view_y = data.player_y - MARGIN_HEIGHT;

			if (data.view_y < 0)
				data.view_y = 0;
		}

		if (data.player_y < 0)
			data.player_y = 0;
	}

	walksprite_update(&data.player_sprite, ticks);
}

static void
handle(const union event *event)
{
	switch (event->type) {
	case EVENT_KEYDOWN:
		handle_keydown(event);
		break;
	case EVENT_KEYUP:
		handle_keyup(event);
		break;
	default:
		break;
	}
}

static void
update(unsigned int ticks)
{
	move(ticks);
}

static void
draw(void)
{
	painter_set_color(0x000000ff);
	painter_clear();
	map_repaint(&data.map);
	map_draw(&data.map, data.view_x, data.view_y);
	walksprite_draw(&data.player_sprite, data.player_orientation, data.player_x - data.view_x, data.player_y - data.view_y);
	painter_present();
}

void
map_state_start(struct map *map, struct sprite *s)
{
	data.map = *map;

	walksprite_init(&data.player_sprite, s, 250);
	game_switch(&map_state);
}

struct state map_state = {
	.enter = enter,
	.leave = leave,
	.update = update,
	.handle = handle,
	.draw = draw
};