/* This file is part of Minitrem. Minitrem 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 2 of the License, or (at your option) any later version. Minitrem 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 Minitrem. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include "math.hpp" #define COUNT(A) (sizeof(A) / sizeof((A)[0])) extern bool debug_draw_cmodels; extern bool debug_draw_paths; extern bool debug_draw_tile_coords; extern bool debug_AI; // time in nanoseconds // nclock() never returns 0, so it can be used as a null value typedef uint64_t ntime_t; ntime_t nclock(void); namespace procgen { class prng_t { uint32_t state = 0; public: void seed(uint32_t seed); uint32_t next(void); float next_float(void); void unit_vec(float out[2]); v2f_t unit_vec2(void); }; class perlin_noise_t { size_t size; float (*table)[2] = nullptr; float table_dot(size_t nx, size_t ny, float dx, float dy); public: ~perlin_noise_t(); void generate(prng_t *prng, size_t size); float get(v2f_t x, float scale); }; } namespace render { class state_t; } namespace world { #define SECTOR_SIZE 8 typedef int64_t coord_t; typedef vec_t sector_index_t; typedef vec_t tile_index_t; static tile_index_t neighbor_offsets[8] = { {+1, 0}, {+1, +1}, {0, +1}, {-1, +1}, {-1, 0}, {-1, -1}, {0, -1}, {+1, -1} }; class tile_t { public: unsigned type : 8; unsigned neighbors : 8; unsigned variant : 8; }; sector_index_t sector_index_at(v2f_t x); tile_index_t tile_index_at(v2f_t x); class entity_t; typedef enum { SECTOR_EMPTY, SECTOR_PARTIAL, SECTOR_FULL } sector_level_t; class sector_t { public: sector_index_t index; rectf_t bounds; std::unordered_set ents; sector_level_t level; tile_t tiles[SECTOR_SIZE * SECTOR_SIZE]; }; typedef int cflags_t; typedef struct { cflags_t cflags; rectf_t bounds; } cmodel_t; typedef struct { bool hit; v2f_t end; float frac; } trace_t; void register_tile(uint8_t type, cflags_t cflags); class world_t { procgen::prng_t prng; std::map sectors; void generate_tile(tile_t *tile, tile_index_t index); void generate(sector_t *sector, sector_index_t index, sector_level_t level); protected: friend render::state_t; public: procgen::perlin_noise_t perlin; void (*generator)(world_t *world, sector_index_t index, sector_t *sector, bool gen_tiles, bool gen_decos, void *data) = 0; void *generator_data = 0; world_t(void); sector_t *get_sector(sector_index_t index, sector_level_t level); tile_t *get_tile(tile_index_t index, sector_level_t level); bool find_path(v2f_t src, v2f_t dst, cmodel_t *cmodel, entity_t *ignore, std::list *path); // FIXME: iterators instead of returning std::lists std::list get_sectors(rectf_t rect, sector_level_t level); std::list get_entities(rectf_t rect, cflags_t cflags); std::list get_render_entities(rectf_t rect); bool test_rect(const cmodel_t *cmodel, const entity_t *ignore); trace_t trace(v2f_t start, v2f_t end, cflags_t cflags); struct { size_t sectors = 0, tiles = 0; size_t entities = 0; } stats; }; class entity_t { std::vector parents; void link_to_sector(sector_t *sector); protected: friend world_t; size_t cookie = 0; public: world_t *world = 0; int type; cmodel_t cmodel; rectf_t render_bounds; int render_layer = 0; entity_t(int type_); virtual ~entity_t(void) = 0; void link(world_t *world); void unlink(); virtual void render_to(render::state_t *render) = 0; virtual void render_late_to(render::state_t *render) = 0; }; typedef struct { bool accessible; float dist; } path_node_t; class path_finder_t { path_node_t *node_at(tile_index_t index); bool is_accessible(tile_index_t index); bool diagonal_test(tile_index_t index, size_t i); public: v2f_t src, dst, tile_center; rectf_t bounds; cflags_t cflags; path_node_t *nodes; size_t width, height; tile_index_t base; float shortest_dist; std::deque path, shortest_path; ~path_finder_t(); void setup_nodes(v2f_t src_, v2f_t dst_, cflags_t cflags_); void eliminate_nodes(rectf_t rect); void find_r(tile_index_t index, float dist, float limit); bool find(void); void export_path(std::list *list); }; } namespace interface { class state_t; } namespace game { bool load_assets(void); class entity_t; class unit_t; class effect_t; class die_t { public: size_t count = 1, sides = 20, bonus = 0; die_t(void) = default; die_t(size_t sides_); die_t(size_t count_, size_t sides_); die_t(size_t count_, size_t sides_, size_t bonus_); }; class state_t { void group_say(std::string text); public: world::world_t world; interface::state_t *interface; procgen::prng_t dice_prng; std::unordered_set awake_entities; std::unordered_set selected_units; ntime_t time; // game time double now, dt; // FIXME: refactor the code, use ntime_t everywhere // frame timing data (game runs at a different frequency than the renderer) bool paused; ntime_t t0; size_t frames = 0, frames_since_t0, frames_behind = 0; ntime_t pathfinder_time = 0; void start(void); void stop(void); void tick(ntime_t time); void compute_ambience(render::state_t *render); void pause(void); void resume(void); // These are called by the interface. void select(rectf_t rect); void command(v2f_t x); void spawn_soldier(v2f_t x); size_t roll(die_t die); }; } namespace interface { class state_t { sf::RenderWindow *window; game::state_t *game; float em; struct { v2f_t zero_point_s = v2f_t(0, 0); v2f_t center = v2f_t(0, 0); int zoom = 3; float zoom_s = 3.0f; bool panning = false; sf::Vector2f pan_ref; bool following = true; } camera; struct { bool selecting = false; rectf_t rect; } select; typedef struct { double time; std::string text; } log_entry_t; std::list log; double perf_hist[10] = {0}; size_t perf_hist_index = 0; void start_following(void); void stop_following(void); public: state_t(sf::RenderWindow *window_, game::state_t *game); void tick(double dt); void render_to(render::state_t *render); void print(std::string str); }; } namespace render { class animated_texture_t { friend state_t; protected: sf::Texture *frames = NULL; size_t frame_count; public: ~animated_texture_t(void); bool load(std::string prefix, size_t frame_count_); }; class oriented_sprite_t { protected: friend state_t; animated_texture_t *textures; virtual size_t select_index(float angle, bool *mirror) = 0; public: ~oriented_sprite_t(void); }; class oriented_sprite_4M_t : public oriented_sprite_t { size_t select_index(float angle, bool *mirror); public: void load(std::string prefix, size_t xc, size_t yc, size_t nyc); }; class oriented_sprite_4M2_t : public oriented_sprite_t { size_t select_index(float angle, bool *mirror); public: void load(std::string prefix, size_t xc, size_t yc); }; typedef enum { ALIGN_LEFT_TOP, ALIGN_CENTER_BOTTOM } text_align_t; void register_tile(uint8_t type, const char *top, const char *side, float height); class state_t { sf::RenderWindow *window; void render_tile(world::world_t *world, v2f_t tx, world::tile_t *tile); void drender_text(rectf_t rect, std::string str); void drender_entity(world::entity_t *ent); public: ntime_t time; double dt; struct { size_t sectors, tiles, entities; } stats; state_t(sf::RenderWindow *window_); void begin_frame(ntime_t time_, double dt); void end_frame(void); void render(game::state_t *game); void render(double phase, animated_texture_t *anim, rectf_t bounds, sf::Color color = sf::Color::White, bool mirror = false); void render(double phase, oriented_sprite_t *sprite, rectf_t bounds, float angle); void render_text(v2f_t x, float height, std::string str, text_align_t align, sf::Color color); void render_rect(rectf_t rect, sf::Color color); void render_hlrect(rectf_t rect, sf::Color color); void render_line(v2f_t x0, v2f_t x1, sf::Color color); void debug_path(v2f_t x, std::list *path); rectf_t window_in_world_space(void); }; } namespace audio { void clean_sounds(void); void update_ambience(bool playing); class sound_t { sf::SoundBuffer buffer; public: void load(const char *path); void play(void); }; class ambient_t { public: sf::Music sound; bool playing = false; float weight = 0.0f; void load(const char *path); }; } extern render::state_t *debug_render; // Divide and round to minus infinity. template T divide_rmi(T x, T y, T *rem) { T rv; if (x >= 0) { *rem = x % y; return x / y; } rv = (x + 1) / y - 1; *rem = x - rv * y; return rv; } // Linear interpolation. template T lerp(T a, T b, T x) { return a * (1 - x) + b * x; } // Linear map between intervals. template T remap(T am, T ap, T bm, T bp, T x) { return (x - am) * (bp - bm) / (ap - am) + bm; } template T clamp(T x, T m, T p) { if (x < m) return m; if (x > p) return p; return x; } // Bilinear interpolation. template T bilerp(T a, T b, T c, T d, T x, T y) { T ab, cd; ab = lerp(a, b, x); cd = lerp(c, d, x); return lerp(ab, cd, y); } template static inline void expfade(T *a, T b, T l, T dt) { *a = b + (*a - b) * exp(-l * dt); } template static inline void expfade(vec_t *a, vec_t b, float l, float dt) { *a = b + (*a - b) * exp(-l * dt); }