breakhack/src/map.c

503 lines
12 KiB
C
Raw Normal View History

2018-02-16 18:11:26 +01:00
/*
* BreakHack - A dungeone crawler RPG
* Copyright (C) 2018 Linus Probert <linus.probert@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
2017-12-21 11:57:12 +01:00
#include <stdlib.h>
2017-12-01 16:03:19 +01:00
#include "map.h"
2017-12-02 23:32:40 +01:00
#include "map_lua.h"
2017-12-01 16:03:19 +01:00
#include "util.h"
#include "item.h"
#include "item_builder.h"
#include "gui.h"
2018-03-15 11:30:18 +01:00
#include "particle_engine.h"
2018-05-15 11:16:56 +02:00
#include "update_data.h"
#include "trap.h"
#include "mixer.h"
2017-12-01 16:03:19 +01:00
2018-08-21 15:44:12 +02:00
static Room*
create_room(void)
2017-12-01 16:03:19 +01:00
{
int i, j;
Room *room;
room = ec_malloc(sizeof(Room));
2018-03-15 11:30:18 +01:00
room->modifier.type = RMOD_TYPE_NONE;
room->visited = false;
2019-03-20 09:53:35 +01:00
room->lockTypes = 0;
2017-12-05 08:13:28 +01:00
for (i=0; i < MAP_ROOM_WIDTH; ++i) {
for (j=0; j < MAP_ROOM_HEIGHT; ++j) {
2017-12-01 16:03:19 +01:00
room->tiles[i][j] = NULL;
2019-03-11 16:21:15 +01:00
room->walls[i][j] = NULL;
2017-12-08 09:45:57 +01:00
room->decorations[i][j] = NULL;
room->traps[i][j] = NULL;
room->doors[i][j] = NULL;
2017-12-01 16:03:19 +01:00
}
}
return room;
}
Map*
map_create(void)
2017-12-01 16:03:19 +01:00
{
int i, j;
Map *map = ec_malloc(sizeof(Map));
map->textures = linkedlist_create();
map->monsters = linkedlist_create();
map->items = linkedlist_create();
map->artifacts = linkedlist_create();
2018-08-13 13:11:32 +02:00
map->objects = linkedlist_create();
2017-12-01 16:03:19 +01:00
map->currentRoom = (Position) { 0, 0 };
map->monsterMoveTimer = _timer_create();
2017-12-01 16:03:19 +01:00
map->level = 1;
2019-03-20 09:53:35 +01:00
map->lockTypes = 0;
2017-12-01 16:03:19 +01:00
2017-12-05 08:13:28 +01:00
for (i=0; i < MAP_H_ROOM_COUNT; ++i) {
for (j=0; j < MAP_V_ROOM_COUNT; ++j) {
2017-12-01 16:03:19 +01:00
map->rooms[i][j] = create_room();
}
}
return map;
}
MapTile*
map_create_tile(void)
{
MapTile *tile = ec_malloc(sizeof(MapTile));
tile->sprite = sprite_create();
tile->sprite->clip = CLIP16(0, 0);
tile->sprite->dim = DIM(32, 32);
tile->collider = false;
tile->lethal = false;
tile->lightsource = false;
tile->levelExit = false;
2019-03-11 16:28:57 +01:00
tile->door = false;
tile->lockType = LOCK_NONE;
return tile;
}
static void
map_tile_destroy(MapTile *tile)
{
if (tile->sprite)
sprite_destroy(tile->sprite);
free(tile);
}
2019-03-11 16:21:15 +01:00
static void
switch_tile(Map *map, Position *tile_pos, MapTile *tile, MapTile **oldTile)
2017-12-02 23:32:40 +01:00
{
2019-03-11 16:21:15 +01:00
// Set the decoration sprites position to match tile pos
tile->sprite->pos = POS(tile_pos->x * TILE_DIMENSION + (map->currentRoom.x * GAME_VIEW_WIDTH),
tile_pos->y * TILE_DIMENSION + (map->currentRoom.y * GAME_VIEW_HEIGHT));
2017-12-02 23:32:40 +01:00
if (*oldTile != NULL) {
map_tile_destroy(*oldTile);
2017-12-02 23:32:40 +01:00
*oldTile = NULL;
}
*oldTile = tile;
}
2019-03-11 16:21:15 +01:00
void
map_add_tile(Map *map, Position *tile_pos, MapTile *tile)
2017-12-08 09:45:57 +01:00
{
const Position *cr = &map->currentRoom;
2019-03-11 16:21:15 +01:00
Room *room = map->rooms[cr->x][cr->y];
switch_tile(map, tile_pos, tile, &room->tiles[tile_pos->x][tile_pos->y]);
2019-03-11 16:21:15 +01:00
// If this is the level exit then clear the decoration if one exists
if (tile->levelExit && room->decorations[tile_pos->x][tile_pos->y]) {
MapTile **decoration = &room->decorations[tile_pos->x][tile_pos->y];
map_tile_destroy(*decoration);
*decoration = NULL;
2017-12-08 09:45:57 +01:00
}
}
void
2019-03-11 16:21:15 +01:00
map_add_wall(Map *map, Position *tile_pos, MapTile *tile)
{
const Position *cr = &map->currentRoom;
2019-03-11 16:21:15 +01:00
Room *room = map->rooms[cr->x][cr->y];
switch_tile(map, tile_pos, tile, &room->walls[tile_pos->x][tile_pos->y]);
}
2019-03-11 16:21:15 +01:00
void map_add_decoration(Map *map, Position *tile_pos, MapTile *tile)
{
const Position *cr = &map->currentRoom;
Room *room = map->rooms[cr->x][cr->y];
switch_tile(map, tile_pos, tile, &room->decorations[tile_pos->x][tile_pos->y]);
}
2019-03-11 16:21:15 +01:00
void
map_add_door(Map *map, Position *tile_pos, MapTile *tile)
{
const Position *cr = &map->currentRoom;
Room *room = map->rooms[cr->x][cr->y];
switch_tile(map, tile_pos, tile, &room->doors[tile_pos->x][tile_pos->y]);
2019-03-11 16:22:39 +01:00
tile->door = true;
2019-03-14 19:11:25 +01:00
tile->sprite->texture_index = 0;
2019-03-11 16:28:57 +01:00
tile->sprite->animate = false;
2019-03-20 09:53:35 +01:00
map->lockTypes |= tile->lockType;
room->lockTypes |= tile->lockType;
}
void
map_add_trap(Map *map, Position *pos, Trap *trap)
{
const Position *cr = &map->currentRoom;
Trap **oldTrap = &map->rooms[cr->x][cr->y]->traps[pos->x][pos->y];
if (*oldTrap)
trap_destroy(*oldTrap);
*oldTrap = trap;
}
2018-09-13 08:05:17 +02:00
bool
2018-08-13 13:11:32 +02:00
map_clear_expired_entities(Map *map, Player *player)
2017-12-15 08:08:45 +01:00
{
2018-08-13 13:11:32 +02:00
LinkedList *filtered = linkedlist_create();
2018-09-13 08:05:17 +02:00
bool anythingCleared = false;
2018-01-25 10:45:05 +01:00
while (map->monsters) {
Monster *monster = linkedlist_pop(&map->monsters);
if (monster->stats.hp <= 0) {
monster_drop_loot(monster, map, player);
monster_destroy(monster);
2018-09-13 08:05:17 +02:00
anythingCleared = true;
} else {
2018-08-13 13:11:32 +02:00
linkedlist_append(&filtered, monster);
}
}
2018-08-13 13:11:32 +02:00
map->monsters = filtered;
2018-08-13 13:11:32 +02:00
filtered = linkedlist_create();
while (map->items) {
Item *item = linkedlist_pop(&map->items);
2018-09-13 08:05:17 +02:00
if (item->collected) {
item_destroy(item);
2018-09-13 08:05:17 +02:00
anythingCleared = true;
} else {
linkedlist_append(&filtered, item);
2018-09-13 08:05:17 +02:00
}
2017-12-15 08:08:45 +01:00
}
map->items = filtered;
2017-12-15 08:08:45 +01:00
2018-08-13 13:11:32 +02:00
filtered = linkedlist_create();
while (map->artifacts) {
Artifact *a = linkedlist_pop(&map->artifacts);
2018-09-13 08:05:17 +02:00
if (!a->collected) {
linkedlist_append(&filtered, a);
2018-09-13 08:05:17 +02:00
} else {
artifact_destroy(a);
2018-09-13 08:05:17 +02:00
anythingCleared = true;
}
}
map->artifacts = filtered;
2018-08-13 13:11:32 +02:00
filtered = linkedlist_create();
while (map->objects) {
Object *o = linkedlist_pop(&map->objects);
2018-09-13 08:05:17 +02:00
if (o->dead) {
2018-08-13 13:11:32 +02:00
object_destroy(o);
2018-09-13 08:05:17 +02:00
anythingCleared = true;
} else {
2018-08-13 13:11:32 +02:00
linkedlist_append(&filtered, o);
2018-09-13 08:05:17 +02:00
}
2018-08-13 13:11:32 +02:00
}
map->objects = filtered;
2018-09-13 08:05:17 +02:00
return anythingCleared;
}
void
map_add_monster(Map *map, Monster *m)
{
monster_update_stats_for_level(m, map->level);
linkedlist_append(&map->monsters, m);
}
2018-02-19 15:09:04 +01:00
bool
2017-12-17 13:43:41 +01:00
map_move_monsters(Map *map, RoomMatrix *rm)
{
LinkedList *m = map->monsters;
2018-02-19 15:09:04 +01:00
bool allDone = true;
if (timer_started(map->monsterMoveTimer)
&& timer_get_ticks(map->monsterMoveTimer) < 100)
return false;
2017-12-17 13:43:41 +01:00
while (m) {
Monster *monster = m->data;
m = m->next;
if (monster->stats.hp <= 0)
continue;
2017-12-17 13:43:41 +01:00
if (!position_in_room(&monster->sprite->pos, &map->currentRoom))
continue;
// Prevent passive monsters from being "dodgy"
Position pos = position_to_matrix_coords(&monster->sprite->pos);
if (monster->state.current == PASSIVE
&& position_proximity(1, &rm->playerRoomPos, &pos))
continue;
if (monster->steps >= monster->stats.speed)
continue;
2018-08-13 13:11:32 +02:00
allDone = allDone && monster_move(monster, rm, map);
2017-12-17 13:43:41 +01:00
}
2018-02-19 15:09:04 +01:00
if (allDone) {
2018-02-19 15:09:04 +01:00
timer_stop(map->monsterMoveTimer);
linkedlist_each(&map->monsters, (void (*)(void*)) monster_reset_steps);
} else {
2018-02-19 15:09:04 +01:00
timer_start(map->monsterMoveTimer);
}
2018-02-19 15:09:04 +01:00
return allDone;
2017-12-17 13:43:41 +01:00
}
2017-12-02 23:32:40 +01:00
int map_add_texture(Map *map, const char *path, SDL_Renderer *renderer)
{
2017-12-15 15:03:29 +01:00
Texture *t = texture_create();
texture_load_from_file(t, path, renderer);
linkedlist_append(&map->textures, t);
2017-12-02 23:32:40 +01:00
return linkedlist_size(map->textures) - 1;
}
static
void map_tile_render(MapTile *tile, Camera *cam)
{
if (tile == NULL)
return;
2017-12-02 23:32:40 +01:00
sprite_render(tile->sprite, cam);
}
2018-08-13 13:11:32 +02:00
void
map_on_new_turn(Map *map)
{
LinkedList *objects = map->objects;
while (objects) {
Object *o = objects->data;
objects = objects->next;
object_step(o);
}
}
2018-05-15 11:16:56 +02:00
void
map_update(UpdateData *data)
{
Map *map = data->map;
LinkedList *monster = map->monsters;
while (monster) {
Monster *m = monster->data;
monster = monster->next;
monster_update(m, data);
}
Position roomPos = { map->currentRoom.x, map->currentRoom.y };
Room *room = map->rooms[roomPos.x][roomPos.y];
for (size_t i=0; i < MAP_ROOM_WIDTH; ++i) {
for (size_t j=0; j < MAP_ROOM_HEIGHT; ++j) {
2018-10-15 22:19:23 +02:00
if (room->tiles[i][j])
sprite_update(room->tiles[i][j]->sprite, data);
}
}
2019-02-28 21:53:30 +01:00
LinkedList *items = map->items;
while (items) {
item_update(items->data);
items = items->next;
}
2018-05-15 11:16:56 +02:00
}
2017-12-01 16:03:19 +01:00
void map_render(Map *map, Camera *cam)
{
2017-12-17 13:43:41 +01:00
unsigned int i, j;
2017-12-08 14:40:33 +01:00
2017-12-01 16:03:19 +01:00
Position roomPos = { map->currentRoom.x, map->currentRoom.y };
Room *room = map->rooms[roomPos.x][roomPos.y];
2017-12-05 08:13:28 +01:00
for (i=0; i < MAP_ROOM_WIDTH; ++i) {
for (j=0; j < MAP_ROOM_HEIGHT; ++j) {
map_tile_render(room->tiles[i][j], cam);
2019-03-11 16:21:15 +01:00
map_tile_render(room->walls[i][j], cam);
map_tile_render(room->doors[i][j], cam);
2019-03-11 16:21:15 +01:00
map_tile_render(room->decorations[i][j], cam);
if (room->traps[i][j])
trap_render(room->traps[i][j], cam);
2017-12-01 16:03:19 +01:00
}
}
2018-03-15 11:30:18 +01:00
if (room->modifier.type == RMOD_TYPE_WINDY) {
particle_engine_wind(room->modifier.data.wind.direction);
2018-08-13 13:11:32 +02:00
} else if (room->modifier.type == RMOD_TYPE_FIRE) {
particle_engine_heat();
2018-03-15 11:30:18 +01:00
}
}
void
map_render_mid_layer(Map *map, Camera *cam)
{
LinkedList *items = map->items;
while (items != NULL) {
item_render(items->data, cam);
items = items->next;
}
2018-08-13 13:11:32 +02:00
LinkedList *objects = map->objects;
while (objects != NULL) {
object_render(objects->data, cam);
objects = objects->next;
}
LinkedList *artifacts = map->artifacts;
while (artifacts != NULL) {
artifact_render(artifacts->data, cam);
artifacts = artifacts->next;
}
LinkedList *monsterItem = map->monsters;
while (monsterItem != NULL) {
Monster *monster = monsterItem->data;
monsterItem = monsterItem->next;
monster_render(monster, cam);
}
}
void
map_render_top_layer(Map *map, RoomMatrix *rm, Camera *cam)
{
LinkedList *monsterItem = map->monsters;
while (monsterItem != NULL) {
Monster *monster = monsterItem->data;
monsterItem = monsterItem->next;
monster_render_top_layer(monster, rm, cam);
}
2017-12-01 16:03:19 +01:00
}
2017-12-03 11:09:57 +01:00
void map_set_current_room(Map *map, Position *pos)
{
unsigned int room_width, room_height;
room_width = MAP_ROOM_WIDTH * TILE_DIMENSION;
room_height = MAP_ROOM_HEIGHT * TILE_DIMENSION;
if (pos->x <= 0) {
map->currentRoom.x = 0;
} else {
unsigned int room_cord_x = pos->x - (pos->x % room_width);
map->currentRoom.x = room_cord_x / room_width;
}
if (pos->y <= 0) {
map->currentRoom.y = 0;
} else {
unsigned int room_cord_y = pos->y - (pos->y % room_height);
map->currentRoom.y = room_cord_y / room_height;
}
2017-12-05 08:13:28 +01:00
if (map->currentRoom.x >= MAP_H_ROOM_COUNT)
map->currentRoom.x = MAP_H_ROOM_COUNT - 1;
if (map->currentRoom.y >= MAP_V_ROOM_COUNT)
map->currentRoom.y = MAP_V_ROOM_COUNT - 1;
2018-08-21 15:44:12 +02:00
map->rooms[map->currentRoom.x][map->currentRoom.y]->visited = true;
2017-12-03 11:09:57 +01:00
}
2017-12-01 16:03:19 +01:00
static
void map_room_destroy(Room *room)
{
int i, j;
2017-12-05 08:13:28 +01:00
for (i=0; i < MAP_ROOM_WIDTH; ++i) {
for (j=0; j < MAP_ROOM_HEIGHT; ++j) {
2017-12-01 16:03:19 +01:00
if (room->tiles[i][j]) {
map_tile_destroy(room->tiles[i][j]);
2017-12-01 16:03:19 +01:00
}
if (room->doors[i][j]) {
map_tile_destroy(room->doors[i][j]);
}
if (room->walls[i][j]) {
map_tile_destroy(room->walls[i][j]);
}
2017-12-08 09:45:57 +01:00
if (room->decorations[i][j]) {
map_tile_destroy(room->decorations[i][j]);
2017-12-08 09:45:57 +01:00
}
if (room->traps[i][j]) {
trap_destroy(room->traps[i][j]);
}
2017-12-01 16:03:19 +01:00
}
}
free(room);
}
void
map_trigger_tile_fall(MapTile *tile)
{
if (tile->sprite->state != SPRITE_STATE_FALLING && tile->sprite->state != SPRITE_STATE_PLUMMETED)
particle_engine_dust_puff(tile->sprite->pos, tile->sprite->dim);
tile->sprite->state = SPRITE_STATE_FALLING;
tile->lethal = true;
}
bool
map_open_door(MapTile *tile, Player *player)
{
if (tile->lockType == LOCK_NONE || tile->lockType & player->equipment.keys) {
// Open the door
2019-03-19 21:54:58 +01:00
if (tile->lockType != LOCK_NONE)
gui_log("You unlocked a door!");
mixer_play_effect(DOOR_OPEN);
tile->sprite->texture_index = 1;
tile->collider = false;
return true;
}
return false;
}
void
map_destroy(Map *map)
2017-12-01 16:03:19 +01:00
{
int i, j;
2017-12-05 08:13:28 +01:00
for (i=0; i < MAP_H_ROOM_COUNT; ++i) {
for (j=0; j < MAP_V_ROOM_COUNT; ++j) {
2017-12-01 16:03:19 +01:00
map_room_destroy(map->rooms[i][j]);
}
}
while (map->textures != NULL)
texture_destroy(linkedlist_pop(&map->textures));
while (map->monsters != NULL)
monster_destroy(linkedlist_pop(&map->monsters));
while (map->items != NULL)
item_destroy(linkedlist_pop(&map->items));
while (map->artifacts != NULL)
artifact_destroy(linkedlist_pop(&map->artifacts));
2018-08-13 13:11:32 +02:00
while (map->objects != NULL)
object_destroy(linkedlist_pop(&map->objects));
2018-08-13 13:11:32 +02:00
2018-03-06 13:50:43 +01:00
timer_destroy(map->monsterMoveTimer);
2017-12-01 16:03:19 +01:00
free(map);
}