Page MenuHomePhabricator (Chris)

No OneTemporary

Size
151 KB
Referenced Files
None
Subscribers
None
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 67f04dc..9e1f502 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,10 +1,10 @@
cmake_minimum_required(VERSION 3.14)
project(SDL_Game)
set(CMAKE_CXX_STANDARD 17)
include_directories(include)
find_package(Boost REQUIRED COMPONENTS filesystem)
include_directories(${Boost_INCLUDE_DIRS})
link_directories(lib)
-link_libraries(mingw32 SDL2main SDL2 SDL2_image SDL2_gfx)
-add_executable(SDL_Game src/main.cpp src/Game.cpp src/Game.h src/Entity.h src/Component.h src/System.h src/systems/RenderSystem.cpp src/systems/RenderSystem.h src/components/Visibility.cpp src/components/Visibility.h src/components/Velocity.cpp src/components/Velocity.h src/systems/MovementSystem.cpp src/systems/MovementSystem.h src/components/Position.h src/components/PathIndex.h src/GameData.h src/components/Sequence.cpp src/components/Sequence.h src/systems/BloonsSpawnSystem.cpp src/systems/BloonsSpawnSystem.h src/GameData.cpp src/components/Action.h src/systems/EventSystem.cpp src/systems/EventSystem.h src/components/Draggable.h src/systems/DraggingSystem.cpp src/systems/DraggingSystem.h src/Settings.h src/systems/RemoveEntitiesSystem.cpp src/systems/RemoveEntitiesSystem.h src/components/MoveEntityEvent.h src/components/RangeShadow.h src/Physics.cpp src/Physics.h src/systems/CollisionSystem.cpp src/systems/CollisionSystem.h src/systems/ShotsSpawnSystem.cpp src/systems/ShotsSpawnSystem.h src/components/IntegerComponents.h src/components/FloatComponents.h src/components/AttackSpeed.h src/components/AttackSpeed.cpp src/components/PoppedBloons.h src/systems/DamageSystem.cpp src/systems/DamageSystem.h src/components/FlagComponents.h src/System.cpp src/Entity.cpp src/components/DamageEvent.h)
+link_libraries(mingw32 SDL2main SDL2 SDL2_image SDL2_gfx SDL2_ttf)
+add_executable(SDL_Game src/SDL_FontCache.c src/SDL_FontCache.h src/main.cpp src/Game.cpp src/Game.h src/Entity.h src/Component.h src/System.h src/systems/RenderSystem.cpp src/systems/RenderSystem.h src/components/Visibility.cpp src/components/Visibility.h src/components/Velocity.cpp src/components/Velocity.h src/systems/MovementSystem.cpp src/systems/MovementSystem.h src/components/Position.h src/components/PathIndex.h src/GameData.h src/components/Sequence.cpp src/components/Sequence.h src/systems/BloonsSpawnSystem.cpp src/systems/BloonsSpawnSystem.h src/GameData.cpp src/components/Action.h src/systems/EventSystem.cpp src/systems/EventSystem.h src/components/Draggable.h src/systems/DraggingSystem.cpp src/systems/DraggingSystem.h src/Settings.h src/systems/RemoveEntitiesSystem.cpp src/systems/RemoveEntitiesSystem.h src/components/MoveEntityEvent.h src/components/RangeShadow.h src/Physics.cpp src/Physics.h src/systems/CollisionSystem.cpp src/systems/CollisionSystem.h src/systems/ShotsSpawnSystem.cpp src/systems/ShotsSpawnSystem.h src/components/IntegerComponents.h src/components/FloatComponents.h src/components/AttackSpeed.h src/components/AttackSpeed.cpp src/components/PoppedBloons.h src/systems/DamageSystem.cpp src/systems/DamageSystem.h src/components/FlagComponents.h src/System.cpp src/Entity.cpp src/components/DamageEvent.h)
target_link_libraries(SDL_Game ${Boost_LIBRARIES})
\ No newline at end of file
diff --git a/src/Game.cpp b/src/Game.cpp
index d09477e..9d3bd86 100644
--- a/src/Game.cpp
+++ b/src/Game.cpp
@@ -1,159 +1,159 @@
//
// Created by Ido Mozes on 18/06/2019.
//
#include <array>
#include "Game.h"
using namespace boost::filesystem;
Game::Game(bool fullscreen, float mapScale) {
gameData.mapScale = mapScale;
gameData.fullscreen = fullscreen;
path p = path("../assets/Bloons");
directory_iterator it{p};
for (auto &p :it) {
gameData.assets[p.path().filename().string().substr(0, p.path().filename().string().length() - 4)] = IMG_Load(
p.path().string().c_str());
}
p = path("../assets/Sprites");
it = directory_iterator{p};
for (auto &p :it) {
gameData.assets[p.path().filename().string().substr(0, p.path().filename().string().length() - 4)] = IMG_Load(
p.path().string().c_str());
}
gameData.assets["map"] = IMG_Load("../assets/map0.jpg");
gameData.assets["upgrade_bar"] = IMG_Load("../assets/upgrade_bar.jpg");
gameData.assets["upgrade_bar2"] = IMG_Load("../assets/upgrade_bar2.jpg");
gameData.assets["menu"] = IMG_Load("../assets/menu.jpg");
renderSystem = new RenderSystem();
renderSystem->init(gameData);
loadMap();
std::initializer_list<std::pair<std::string, Point>> sprites[]{
{{"map", {SIDEBAR_WIDTH, 0}}},
{},
{},
{},
{},
{},
{{"upgrade_bar", {0, 0}}, {"menu", {MAP_WIDTH + SIDEBAR_WIDTH, 0}}},
};
for (int i = 0; i < N_LAYERS; i++) {
for (auto &sprite :sprites[i]) {
auto spriteEntity = new Entity();
spriteEntity->addComponent<Visibility>(gameData.renderer, gameData.assets[sprite.first],
SDL_Rect{int(sprite.second.X), int(sprite.second.Y),
gameData.assets[sprite.first]->w,
gameData.assets[sprite.first]->h});
layers[i].emplace_back(spriteEntity);
}
}
// Towers
auto tower = new Entity();
tower->addComponent<Kind>(SUPER_MONKEY);
tower->addComponent<ShotKind>(DART);
SDL_Surface *surface = gameData.assets["SuperMonkey"];
tower->addComponent<Visibility>(gameData.renderer, surface,
- SDL_Rect{SIDEBAR_WIDTH + MAP_WIDTH + 10, 10, int(surface->w / 1.5), 0});
+ SDL_Rect{SIDEBAR_WIDTH + MAP_WIDTH + 10, 100, int(surface->w / 1.5), 0});
tower->addComponent<Action>(DRAG);
tower->addComponent<Range>(100);
tower->addComponent<AttackSpeed>(30);
tower->addComponent<Pierce>(4);
- tower->addComponent<Damage>(21);
+ tower->addComponent<Damage>(100);
tower->addComponent<Distance>(150);
tower->addComponent<Type>(TOWER_T);
layers[MENU_LAYER].emplace_back(tower);
tower = new Entity();
tower->addComponent<Kind>(SNIPER_MONKEY);
- tower->addComponent<ShotKind>(DART);
+ tower->addComponent<ShotKind>(GUN);
surface = gameData.assets["SniperMonkey"];
tower->addComponent<Visibility>(gameData.renderer, surface,
- SDL_Rect{SIDEBAR_WIDTH + MAP_WIDTH + 100, 10, int(surface->w / 1.5), 0});
+ SDL_Rect{SIDEBAR_WIDTH + MAP_WIDTH + 100, 100, int(surface->w / 1.5), 0});
tower->addComponent<Action>(DRAG);
- tower->addComponent<Range>(100);
- tower->addComponent<AttackSpeed>(6);
- tower->addComponent<Pierce>(5);
- tower->addComponent<Damage>(1);
- tower->addComponent<Distance>(300);
+ tower->addComponent<Range>(500);
+ tower->addComponent<AttackSpeed>(2);
+ tower->addComponent<Pierce>(1);
+ tower->addComponent<Damage>(3);
+ tower->addComponent<Distance>(600);
tower->addComponent<Type>(TOWER_T);
layers[MENU_LAYER].emplace_back(tower);
// Sequences
auto s = new Entity();
- s->addComponent<Sequence>(1, 1, 0);
+ s->addComponent<Sequence>(1, 0.5, 0);
s->addComponent<Kind>(BAD);
s->addComponent<Type>(SEQUENCE_T);
layers[SEQUENCES_LAYER].emplace_back(s);
// s = new Entity();
// s->addComponent<Sequence>(100, 20.2, 60 * 5);
// s->addComponent<Kind>(std::string("Blue"));
// s->addComponent<Type>(SEQUENCE_T);
// s->addComponent<Speed>(1.5);
// layers[SEQUENCES_LAYER].emplace_back(s);
// Systems
systems.emplace_back(new BloonsSpawnSystem);
systems.emplace_back(new ShotsSpawnSystem);
systems.emplace_back(new EventSystem);
systems.emplace_back(new DraggingSystem);
systems.emplace_back(new CollisionSystem);
systems.emplace_back(new DamageSystem);
systems.emplace_back(new MovementSystem);
systems.emplace_back(new RemoveEntitiesSystem);
systems.emplace_back(renderSystem);
}
Game::~Game() {
SDL_Quit();
}
void Game::update() {
for (auto &system : systems) {
system->update(layers, gameData);
}
}
void Game::loadMap() {
std::string fileName = "../assets/map" + std::to_string(gameData.map);
std::ifstream obstaclesFile(fileName + "_obstacles.data", std::ios::binary);
int x = 0, y = 0;
for (int i = 0; i < ceilf(MAP_WIDTH * MAP_HEIGHT / 8.0); ++i) {
unsigned char byte;
obstaclesFile.read((char *) &byte, 1);
for (int j = 0; j < 8; ++j) {
char bit = (byte & 2 << j) >> j;
gameData.mapData[x][y] = bit;
x++;
if (x == MAP_WIDTH) {
x = 0;
y++;
}
if (x * y >= MAP_WIDTH * MAP_HEIGHT)
goto readPathFile;
}
}
readPathFile:
gameData.path.clear();
std::ifstream pathFile(fileName + "_path.data", std::ios::binary);
unsigned int length;
TempPoint startingPoint, finishPoint;
pathFile.read((char *) &startingPoint, 4);
pathFile.read((char *) &length, 4);
gameData.path.resize(length);
pathFile.read(&gameData.path[0], length);
pathFile.read((char *) &finishPoint, 4);
gameData.startingPoint = {float(startingPoint.X), float(startingPoint.Y)};
gameData.finishPoint = {float(finishPoint.X), float(finishPoint.Y)};
}
diff --git a/src/GameData.cpp b/src/GameData.cpp
index dff7a83..1a19385 100644
--- a/src/GameData.cpp
+++ b/src/GameData.cpp
@@ -1,12 +1,13 @@
//
// Created by Ido Mozes on 07/07/2019.
//
#include "GameData.h"
GameData::~GameData() {
for(auto & item:assets){
SDL_FreeSurface(item.second);
}
SDL_DestroyWindow(window);
SDL_DestroyRenderer(renderer);
+ FC_FreeFont(font);
}
diff --git a/src/GameData.h b/src/GameData.h
index 77dcacc..2ea6bba 100644
--- a/src/GameData.h
+++ b/src/GameData.h
@@ -1,39 +1,42 @@
//
// Created by Ido Mozes on 03/07/2019.
//
#ifndef SDL_GAME_GAMEDATA_H
#define SDL_GAME_GAMEDATA_H
#include <vector>
#include <memory>
#include <unordered_map>
#include "Component.h"
#include "Entity.h"
#include "Settings.h"
#include "SDL.h"
+#include "SDL_FontCache.h"
+
constexpr char FREE = 0;
constexpr char OBSTACLE = 1;
constexpr char TOWER = 2;
class GameData {
public:
bool isRunning = true;
bool isDragging = false;
+ int money = 0;
int level = 0;
int map = 0;
float mapScale;
bool fullscreen;
std::vector<char> path;
char mapData[MAP_WIDTH][MAP_HEIGHT];
Point startingPoint;
Point finishPoint;
std::unordered_map<std::string, SDL_Surface *> assets;
SDL_Window *window = nullptr;
SDL_Renderer *renderer = nullptr;
EntityP selected;
-
+ FC_Font* font;
~GameData();
};
#endif //SDL_GAME_GAMEDATA_H
diff --git a/src/SDL_FontCache.c b/src/SDL_FontCache.c
new file mode 100644
index 0000000..0afe820
--- /dev/null
+++ b/src/SDL_FontCache.c
@@ -0,0 +1,2822 @@
+/*
+SDL_FontCache: A font cache for SDL and SDL_ttf
+by Jonathan Dearborn
+
+See SDL_FontCache.h for license info.
+*/
+
+#include "SDL_FontCache.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+// Visual C does not support static inline
+#ifndef static_inline
+ #ifdef _MSC_VER
+ #define static_inline static
+ #else
+ #define static_inline static inline
+ #endif
+#endif
+
+#if SDL_VERSION_ATLEAST(2,0,0)
+ #define FC_GET_ALPHA(sdl_color) ((sdl_color).a)
+#else
+ #define FC_GET_ALPHA(sdl_color) ((sdl_color).unused)
+#endif
+
+// Need SDL_RenderIsClipEnabled() for proper clipping support
+#if SDL_VERSION_ATLEAST(2,0,4)
+ #define ENABLE_SDL_CLIPPING
+#endif
+
+#define FC_MIN(a,b) ((a) < (b)? (a) : (b))
+#define FC_MAX(a,b) ((a) > (b)? (a) : (b))
+
+
+// vsnprintf replacement from Valentin Milea:
+// http://stackoverflow.com/questions/2915672/snprintf-and-visual-studio-2010
+#if defined(_MSC_VER) && _MSC_VER < 1900
+
+#define snprintf c99_snprintf
+#define vsnprintf c99_vsnprintf
+
+__inline int c99_vsnprintf(char *outBuf, size_t size, const char *format, va_list ap)
+{
+ int count = -1;
+
+ if (size != 0)
+ count = _vsnprintf_s(outBuf, size, _TRUNCATE, format, ap);
+ if (count == -1)
+ count = _vscprintf(format, ap);
+
+ return count;
+}
+
+__inline int c99_snprintf(char *outBuf, size_t size, const char *format, ...)
+{
+ int count;
+ va_list ap;
+
+ va_start(ap, format);
+ count = c99_vsnprintf(outBuf, size, format, ap);
+ va_end(ap);
+
+ return count;
+}
+
+#endif
+
+
+#define FC_EXTRACT_VARARGS(buffer, start_args) \
+{ \
+ va_list lst; \
+ va_start(lst, start_args); \
+ vsnprintf(buffer, fc_buffer_size, start_args, lst); \
+ va_end(lst); \
+}
+
+// Extra pixels of padding around each glyph to avoid linear filtering artifacts
+#define FC_CACHE_PADDING 1
+
+
+
+static Uint8 has_clip(FC_Target* dest)
+{
+ #ifdef FC_USE_SDL_GPU
+ return dest->use_clip_rect;
+ #elif defined(ENABLE_SDL_CLIPPING)
+ return SDL_RenderIsClipEnabled(dest);
+ #else
+ return 0;
+ #endif
+}
+
+static FC_Rect get_clip(FC_Target* dest)
+{
+ #ifdef FC_USE_SDL_GPU
+ return dest->clip_rect;
+ #elif defined(ENABLE_SDL_CLIPPING)
+ SDL_Rect r;
+ SDL_RenderGetClipRect(dest, &r);
+ return r;
+ #else
+ SDL_Rect r = {0, 0, 0, 0};
+ return r;
+ #endif
+}
+
+static void set_clip(FC_Target* dest, FC_Rect* rect)
+{
+ #ifdef FC_USE_SDL_GPU
+ if(rect != NULL)
+ GPU_SetClipRect(dest, *rect);
+ else
+ GPU_UnsetClip(dest);
+ #elif defined(ENABLE_SDL_CLIPPING)
+ SDL_RenderSetClipRect(dest, rect);
+ #endif
+}
+
+static void set_color(FC_Image* src, Uint8 r, Uint8 g, Uint8 b, Uint8 a)
+{
+ #ifdef FC_USE_SDL_GPU
+ GPU_SetRGBA(src, r, g, b, a);
+ #else
+ SDL_SetTextureColorMod(src, r, g, b);
+ SDL_SetTextureAlphaMod(src, a);
+ #endif
+}
+
+
+
+static char* new_concat(const char* a, const char* b)
+{
+ // Create new buffer
+ unsigned int size = strlen(a) + strlen(b);
+ char* new_string = (char*)malloc(size+1);
+
+ // Concatenate strings in the new buffer
+ strcpy(new_string, a);
+ strcat(new_string, b);
+
+ return new_string;
+}
+
+static char* replace_concat(char** a, const char* b)
+{
+ char* new_string = new_concat(*a, b);
+ free(*a);
+ *a = new_string;
+ return *a;
+}
+
+
+
+
+
+// Shared buffer for variadic text
+static char* fc_buffer = NULL;
+static unsigned int fc_buffer_size = 1024;
+
+static Uint8 fc_has_render_target_support = 0;
+
+char* FC_GetStringASCII(void)
+{
+ static char* buffer = NULL;
+ if(buffer == NULL)
+ {
+ int i;
+ char c;
+ buffer = (char*)malloc(512);
+ memset(buffer, 0, 512);
+ i = 0;
+ c = 32;
+ while(1)
+ {
+ buffer[i] = c;
+ if(c == 126)
+ break;
+ ++i;
+ ++c;
+ }
+ }
+ return U8_strdup(buffer);
+}
+
+char* FC_GetStringLatin1(void)
+{
+ static char* buffer = NULL;
+ if(buffer == NULL)
+ {
+ int i;
+ unsigned char c;
+ buffer = (char*)malloc(512);
+ memset(buffer, 0, 512);
+ i = 0;
+ c = 0xA0;
+ while(1)
+ {
+ buffer[i] = 0xC2;
+ buffer[i+1] = c;
+ if(c == 0xBF)
+ break;
+ i += 2;
+ ++c;
+ }
+ i += 2;
+ c = 0x80;
+ while(1)
+ {
+ buffer[i] = 0xC3;
+ buffer[i+1] = c;
+ if(c == 0xBF)
+ break;
+ i += 2;
+ ++c;
+ }
+ }
+ return U8_strdup(buffer);
+}
+
+char* FC_GetStringASCII_Latin1(void)
+{
+ static char* buffer = NULL;
+ if(buffer == NULL)
+ buffer = new_concat(FC_GetStringASCII(), FC_GetStringLatin1());
+
+ return U8_strdup(buffer);
+}
+
+FC_Rect FC_MakeRect(float x, float y, float w, float h)
+{
+ FC_Rect r = {x, y, w, h};
+ return r;
+}
+
+FC_Scale FC_MakeScale(float x, float y)
+{
+ FC_Scale s = {x, y};
+
+ return s;
+}
+
+SDL_Color FC_MakeColor(Uint8 r, Uint8 g, Uint8 b, Uint8 a)
+{
+ SDL_Color c = {r, g, b, a};
+
+ return c;
+}
+
+FC_Effect FC_MakeEffect(FC_AlignEnum alignment, FC_Scale scale, SDL_Color color)
+{
+ FC_Effect e;
+
+ e.alignment = alignment;
+ e.scale = scale;
+ e.color = color;
+
+ return e;
+}
+
+FC_GlyphData FC_MakeGlyphData(int cache_level, Sint16 x, Sint16 y, Uint16 w, Uint16 h)
+{
+ FC_GlyphData gd;
+
+ gd.rect.x = x;
+ gd.rect.y = y;
+ gd.rect.w = w;
+ gd.rect.h = h;
+ gd.cache_level = cache_level;
+
+ return gd;
+}
+
+// Enough to hold all of the ascii characters and some.
+#define FC_DEFAULT_NUM_BUCKETS 300
+
+typedef struct FC_MapNode
+{
+ Uint32 key;
+ FC_GlyphData value;
+ struct FC_MapNode* next;
+
+} FC_MapNode;
+
+typedef struct FC_Map
+{
+ int num_buckets;
+ FC_MapNode** buckets;
+} FC_Map;
+
+
+
+static FC_Map* FC_MapCreate(int num_buckets)
+{
+ int i;
+ FC_Map* map = (FC_Map*)malloc(sizeof(FC_Map));
+
+ map->num_buckets = num_buckets;
+ map->buckets = (FC_MapNode**)malloc(num_buckets * sizeof(FC_MapNode*));
+
+ for(i = 0; i < num_buckets; ++i)
+ {
+ map->buckets[i] = NULL;
+ }
+
+ return map;
+}
+
+/*static void FC_MapClear(FC_Map* map)
+{
+ int i;
+ if(map == NULL)
+ return;
+
+ // Go through each bucket
+ for(i = 0; i < map->num_buckets; ++i)
+ {
+ // Delete the nodes in order
+ FC_MapNode* node = map->buckets[i];
+ while(node != NULL)
+ {
+ FC_MapNode* last = node;
+ node = node->next;
+ free(last);
+ }
+ // Set the bucket to empty
+ map->buckets[i] = NULL;
+ }
+}*/
+
+static void FC_MapFree(FC_Map* map)
+{
+ int i;
+ if(map == NULL)
+ return;
+
+ // Go through each bucket
+ for(i = 0; i < map->num_buckets; ++i)
+ {
+ // Delete the nodes in order
+ FC_MapNode* node = map->buckets[i];
+ while(node != NULL)
+ {
+ FC_MapNode* last = node;
+ node = node->next;
+ free(last);
+ }
+ }
+
+ free(map->buckets);
+ free(map);
+}
+
+// Note: Does not handle duplicates in any special way.
+static FC_GlyphData* FC_MapInsert(FC_Map* map, Uint32 codepoint, FC_GlyphData glyph)
+{
+ Uint32 index;
+ FC_MapNode* node;
+ if(map == NULL)
+ return NULL;
+
+ // Get index for bucket
+ index = codepoint % map->num_buckets;
+
+ // If this bucket is empty, create a node and return its value
+ if(map->buckets[index] == NULL)
+ {
+ node = map->buckets[index] = (FC_MapNode*)malloc(sizeof(FC_MapNode));
+ node->key = codepoint;
+ node->value = glyph;
+ node->next = NULL;
+ return &node->value;
+ }
+
+ for(node = map->buckets[index]; node != NULL; node = node->next)
+ {
+ // Find empty node and add a new one on.
+ if(node->next == NULL)
+ {
+ node->next = (FC_MapNode*)malloc(sizeof(FC_MapNode));
+ node = node->next;
+
+ node->key = codepoint;
+ node->value = glyph;
+ node->next = NULL;
+ return &node->value;
+ }
+ }
+
+ return NULL;
+}
+
+static FC_GlyphData* FC_MapFind(FC_Map* map, Uint32 codepoint)
+{
+ Uint32 index;
+ FC_MapNode* node;
+ if(map == NULL)
+ return NULL;
+
+ // Get index for bucket
+ index = codepoint % map->num_buckets;
+
+ // Go through list until we find a match
+ for(node = map->buckets[index]; node != NULL; node = node->next)
+ {
+ if(node->key == codepoint)
+ return &node->value;
+ }
+
+ return NULL;
+}
+
+
+
+struct FC_Font
+{
+ #ifndef FC_USE_SDL_GPU
+ SDL_Renderer* renderer;
+ #endif
+
+ TTF_Font* ttf_source; // TTF_Font source of characters
+ Uint8 owns_ttf_source; // Can we delete the TTF_Font ourselves?
+
+ FC_FilterEnum filter;
+
+ SDL_Color default_color;
+ Uint16 height;
+
+ Uint16 maxWidth;
+ Uint16 baseline;
+ int ascent;
+ int descent;
+
+ int lineSpacing;
+ int letterSpacing;
+
+ // Uses 32-bit (4-byte) Unicode codepoints to refer to each glyph
+ // Codepoints are little endian (reversed from UTF-8) so that something like 0x00000005 is ASCII 5 and the map can be indexed by ASCII values
+ FC_Map* glyphs;
+
+ FC_GlyphData last_glyph; // Texture packing cursor
+ int glyph_cache_size;
+ int glyph_cache_count;
+ FC_Image** glyph_cache;
+
+ char* loading_string;
+
+};
+
+// Private
+static FC_GlyphData* FC_PackGlyphData(FC_Font* font, Uint32 codepoint, Uint16 width, Uint16 maxWidth, Uint16 maxHeight);
+
+
+static FC_Rect FC_RenderLeft(FC_Font* font, FC_Target* dest, float x, float y, FC_Scale scale, const char* text);
+static FC_Rect FC_RenderCenter(FC_Font* font, FC_Target* dest, float x, float y, FC_Scale scale, const char* text);
+static FC_Rect FC_RenderRight(FC_Font* font, FC_Target* dest, float x, float y, FC_Scale scale, const char* text);
+
+
+static_inline SDL_Surface* FC_CreateSurface32(Uint32 width, Uint32 height)
+{
+ #if SDL_BYTEORDER == SDL_BIG_ENDIAN
+ return SDL_CreateRGBSurface(SDL_SWSURFACE, width, height, 32, 0xFF000000, 0x00FF0000, 0x0000FF00, 0x000000FF);
+ #else
+ return SDL_CreateRGBSurface(SDL_SWSURFACE, width, height, 32, 0x000000FF, 0x0000FF00, 0x00FF0000, 0xFF000000);
+ #endif
+}
+
+
+char* U8_alloc(unsigned int size)
+{
+ char* result;
+ if(size == 0)
+ return NULL;
+
+ result = (char*)malloc(size);
+ result[0] = '\0';
+
+ return result;
+}
+
+void U8_free(char* string)
+{
+ free(string);
+}
+
+char* U8_strdup(const char* string)
+{
+ char* result;
+ if(string == NULL)
+ return NULL;
+
+ result = (char*)malloc(strlen(string)+1);
+ strcpy(result, string);
+
+ return result;
+}
+
+int U8_strlen(const char* string)
+{
+ int length = 0;
+ if(string == NULL)
+ return 0;
+
+ while(*string != '\0')
+ {
+ string = U8_next(string);
+ ++length;
+ }
+
+ return length;
+}
+
+int U8_charsize(const char* character)
+{
+ if(character == NULL)
+ return 0;
+
+ if((unsigned char)*character <= 0x7F)
+ return 1;
+ else if((unsigned char)*character < 0xE0)
+ return 2;
+ else if((unsigned char)*character < 0xF0)
+ return 3;
+ else
+ return 4;
+ return 1;
+}
+
+int U8_charcpy(char* buffer, const char* source, int buffer_size)
+{
+ int charsize;
+ if(buffer == NULL || source == NULL || buffer_size < 1)
+ return 0;
+
+ charsize = U8_charsize(source);
+ if(charsize > buffer_size)
+ return 0;
+
+ memcpy(buffer, source, charsize);
+ return charsize;
+}
+
+const char* U8_next(const char* string)
+{
+ return string + U8_charsize(string);
+}
+
+int U8_strinsert(char* string, int position, const char* source, int max_bytes)
+{
+ int pos_u8char;
+ int len;
+ int add_len;
+ int ulen;
+ const char* string_start = string;
+
+ if(string == NULL || source == NULL)
+ return 0;
+
+ len = strlen(string);
+ add_len = strlen(source);
+ ulen = U8_strlen(string);
+
+ if(position == -1)
+ position = ulen;
+
+ if(position < 0 || position > ulen || len + add_len + 1 > max_bytes)
+ return 0;
+
+ // Move string pointer to the proper position
+ pos_u8char = 0;
+ while(*string != '\0' && pos_u8char < position)
+ {
+ string = (char*)U8_next(string);
+ ++pos_u8char;
+ }
+
+ // Move the rest of the string out of the way
+ memmove(string + add_len, string, len - (string - string_start) + 1);
+
+ // Copy in the new characters
+ memcpy(string, source, add_len);
+
+ return 1;
+}
+
+void U8_strdel(char* string, int position)
+{
+ if(string == NULL || position < 0)
+ return;
+
+ while(*string != '\0')
+ {
+ if(position == 0)
+ {
+ int chars_to_erase = U8_charsize(string);
+ int remaining_bytes = strlen(string) + 1;
+ memmove(string, string + chars_to_erase, remaining_bytes);
+ break;
+ }
+
+ string = (char*)U8_next(string);
+ --position;
+ }
+}
+
+
+
+
+
+static_inline FC_Rect FC_RectUnion(FC_Rect A, FC_Rect B)
+{
+ float x,x2,y,y2;
+ x = FC_MIN(A.x, B.x);
+ y = FC_MIN(A.y, B.y);
+ x2 = FC_MAX(A.x+A.w, B.x+B.w);
+ y2 = FC_MAX(A.y+A.h, B.y+B.h);
+ {
+ FC_Rect result = {x, y, FC_MAX(0, x2 - x), FC_MAX(0, y2 - y)};
+ return result;
+ }
+}
+
+// Adapted from SDL_IntersectRect
+static_inline FC_Rect FC_RectIntersect(FC_Rect A, FC_Rect B)
+{
+ FC_Rect result;
+ float Amin, Amax, Bmin, Bmax;
+
+ // Horizontal intersection
+ Amin = A.x;
+ Amax = Amin + A.w;
+ Bmin = B.x;
+ Bmax = Bmin + B.w;
+ if(Bmin > Amin)
+ Amin = Bmin;
+ result.x = Amin;
+ if(Bmax < Amax)
+ Amax = Bmax;
+ result.w = Amax - Amin > 0 ? Amax - Amin : 0;
+
+ // Vertical intersection
+ Amin = A.y;
+ Amax = Amin + A.h;
+ Bmin = B.y;
+ Bmax = Bmin + B.h;
+ if(Bmin > Amin)
+ Amin = Bmin;
+ result.y = Amin;
+ if(Bmax < Amax)
+ Amax = Bmax;
+ result.h = Amax - Amin > 0 ? Amax - Amin : 0;
+
+ return result;
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+FC_Rect FC_DefaultRenderCallback(FC_Image* src, FC_Rect* srcrect, FC_Target* dest, float x, float y, float xscale, float yscale)
+{
+ float w = srcrect->w * xscale;
+ float h = srcrect->h * yscale;
+ FC_Rect result;
+
+ // FIXME: Why does the scaled offset look so wrong?
+ #ifdef FC_USE_SDL_GPU
+ {
+ GPU_Rect r = *srcrect;
+ GPU_BlitScale(src, &r, dest, x + xscale*r.w/2.0f, y + r.h/2.0f, xscale, yscale);
+ }
+ #else
+ {
+ SDL_RendererFlip flip = SDL_FLIP_NONE;
+ if(xscale < 0)
+ {
+ xscale = -xscale;
+ flip = (SDL_RendererFlip) ((int)flip | (int)SDL_FLIP_HORIZONTAL);
+ }
+ if(yscale < 0)
+ {
+ yscale = -yscale;
+ flip = (SDL_RendererFlip) ((int)flip | (int)SDL_FLIP_VERTICAL);
+ }
+
+ SDL_Rect r = *srcrect;
+ SDL_Rect dr = {(int)x, (int)y, (int)(xscale*r.w), (int)(yscale*r.h)};
+ SDL_RenderCopyEx(dest, src, &r, &dr, 0, NULL, flip);
+ }
+ #endif
+
+ result.x = x;
+ result.y = y;
+ result.w = w;
+ result.h = h;
+ return result;
+}
+
+static FC_Rect (*fc_render_callback)(FC_Image* src, FC_Rect* srcrect, FC_Target* dest, float x, float y, float xscale, float yscale) = &FC_DefaultRenderCallback;
+
+void FC_SetRenderCallback(FC_Rect (*callback)(FC_Image* src, FC_Rect* srcrect, FC_Target* dest, float x, float y, float xscale, float yscale))
+{
+ if(callback == NULL)
+ fc_render_callback = &FC_DefaultRenderCallback;
+ else
+ fc_render_callback = callback;
+}
+
+void FC_GetUTF8FromCodepoint(char* result, Uint32 codepoint)
+{
+ char a, b, c, d;
+
+ if(result == NULL)
+ return;
+
+ a = (codepoint >> 24) & 0xFF;
+ b = (codepoint >> 16) & 0xFF;
+ c = (codepoint >> 8) & 0xFF;
+ d = codepoint & 0xFF;
+
+ if(a == 0)
+ {
+ if(b == 0)
+ {
+ if(c == 0)
+ {
+ result[0] = d;
+ result[1] = '\0';
+ }
+ else
+ {
+ result[0] = c;
+ result[1] = d;
+ result[2] = '\0';
+ }
+ }
+ else
+ {
+ result[0] = b;
+ result[1] = c;
+ result[2] = d;
+ result[3] = '\0';
+ }
+ }
+ else
+ {
+ result[0] = a;
+ result[1] = b;
+ result[2] = c;
+ result[3] = d;
+ result[4] = '\0';
+ }
+}
+
+Uint32 FC_GetCodepointFromUTF8(const char** c, Uint8 advance_pointer)
+{
+ Uint32 result = 0;
+ const char* str;
+ if(c == NULL || *c == NULL)
+ return 0;
+
+ str = *c;
+ if((unsigned char)*str <= 0x7F)
+ result = *str;
+ else if((unsigned char)*str < 0xE0)
+ {
+ result |= (unsigned char)(*str) << 8;
+ result |= (unsigned char)(*(str+1));
+ if(advance_pointer)
+ *c += 1;
+ }
+ else if((unsigned char)*str < 0xF0)
+ {
+ result |= (unsigned char)(*str) << 16;
+ result |= (unsigned char)(*(str+1)) << 8;
+ result |= (unsigned char)(*(str+2));
+ if(advance_pointer)
+ *c += 2;
+ }
+ else
+ {
+ result |= (unsigned char)(*str) << 24;
+ result |= (unsigned char)(*(str+1)) << 16;
+ result |= (unsigned char)(*(str+2)) << 8;
+ result |= (unsigned char)(*(str+3));
+ if(advance_pointer)
+ *c += 3;
+ }
+ return result;
+}
+
+
+void FC_SetLoadingString(FC_Font* font, const char* string)
+{
+ if(font == NULL)
+ return;
+
+ free(font->loading_string);
+ font->loading_string = U8_strdup(string);
+}
+
+
+unsigned int FC_GetBufferSize(void)
+{
+ return fc_buffer_size;
+}
+
+void FC_SetBufferSize(unsigned int size)
+{
+ free(fc_buffer);
+ if(size > 0)
+ {
+ fc_buffer_size = size;
+ fc_buffer = (char*)malloc(fc_buffer_size);
+ }
+ else
+ fc_buffer = (char*)malloc(fc_buffer_size);
+}
+
+
+
+
+
+// Constructors
+
+static void FC_Init(FC_Font* font)
+{
+ if(font == NULL)
+ return;
+
+ #ifndef FC_USE_SDL_GPU
+ font->renderer = NULL;
+ #endif
+
+ font->ttf_source = NULL;
+ font->owns_ttf_source = 0;
+
+ font->filter = FC_FILTER_NEAREST;
+
+ font->default_color.r = 0;
+ font->default_color.g = 0;
+ font->default_color.b = 0;
+ FC_GET_ALPHA(font->default_color) = 255;
+
+ font->height = 0; // ascent+descent
+
+ font->maxWidth = 0;
+ font->baseline = 0;
+ font->ascent = 0;
+ font->descent = 0;
+
+ font->lineSpacing = 0;
+ font->letterSpacing = 0;
+
+ // Give a little offset for when filtering/mipmaps are used. Depending on mipmap level, this will still not be enough.
+ font->last_glyph.rect.x = FC_CACHE_PADDING;
+ font->last_glyph.rect.y = FC_CACHE_PADDING;
+ font->last_glyph.rect.w = 0;
+ font->last_glyph.rect.h = 0;
+ font->last_glyph.cache_level = 0;
+
+ if(font->glyphs != NULL)
+ FC_MapFree(font->glyphs);
+
+ font->glyphs = FC_MapCreate(FC_DEFAULT_NUM_BUCKETS);
+
+ font->glyph_cache_size = 3;
+ font->glyph_cache_count = 0;
+
+
+ font->glyph_cache = (FC_Image**)malloc(font->glyph_cache_size * sizeof(FC_Image*));
+
+ if (font->loading_string == NULL)
+ font->loading_string = FC_GetStringASCII();
+
+ if(fc_buffer == NULL)
+ fc_buffer = (char*)malloc(fc_buffer_size);
+}
+
+static Uint8 FC_GrowGlyphCache(FC_Font* font)
+{
+ if(font == NULL)
+ return 0;
+ #ifdef FC_USE_SDL_GPU
+ GPU_Image* new_level = GPU_CreateImage(font->height * 12, font->height * 12, GPU_FORMAT_RGBA);
+ GPU_SetAnchor(new_level, 0.5f, 0.5f); // Just in case the default is different
+ #else
+ SDL_Texture* new_level = SDL_CreateTexture(font->renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, font->height * 12, font->height * 12);
+ #endif
+ if(new_level == NULL || !FC_SetGlyphCacheLevel(font, font->glyph_cache_count, new_level))
+ {
+ FC_Log("Error: SDL_FontCache ran out of packing space and could not add another cache level.\n");
+ #ifdef FC_USE_SDL_GPU
+ GPU_FreeImage(new_level);
+ #else
+ SDL_DestroyTexture(new_level);
+ #endif
+ return 0;
+ }
+ // bug: we do not have the correct color here, this might be the wrong color!
+ // , most functions use set_color_for_all_caches()
+ // - for evading this bug, you must use FC_SetDefaultColor(), before using any draw functions
+ set_color(new_level, font->default_color.r, font->default_color.g, font->default_color.b, FC_GET_ALPHA(font->default_color));
+#ifndef FC_USE_SDL_GPU
+ {
+ Uint8 r, g, b, a;
+ SDL_Texture* prev_target = SDL_GetRenderTarget(font->renderer);
+ SDL_Rect prev_clip, prev_viewport;
+ int prev_logicalw, prev_logicalh;
+ Uint8 prev_clip_enabled;
+ float prev_scalex, prev_scaley;
+ // only backup if previous target existed (SDL will preserve them for the default target)
+ if (prev_target) {
+ prev_clip_enabled = has_clip(font->renderer);
+ if (prev_clip_enabled)
+ prev_clip = get_clip(font->renderer);
+ SDL_RenderGetViewport(font->renderer, &prev_viewport);
+ SDL_RenderGetScale(font->renderer, &prev_scalex, &prev_scaley);
+ SDL_RenderGetLogicalSize(font->renderer, &prev_logicalw, &prev_logicalh);
+ }
+ SDL_SetTextureBlendMode(new_level, SDL_BLENDMODE_BLEND);
+ SDL_SetRenderTarget(font->renderer, new_level);
+ SDL_GetRenderDrawColor(font->renderer, &r, &g, &b, &a);
+ SDL_SetRenderDrawColor(font->renderer, 0, 0, 0, 0);
+ SDL_RenderClear(font->renderer);
+ SDL_SetRenderDrawColor(font->renderer, r, g, b, a);
+ SDL_SetRenderTarget(font->renderer, prev_target);
+ if (prev_target) {
+ if (prev_clip_enabled)
+ set_clip(font->renderer, &prev_clip);
+ if (prev_logicalw && prev_logicalh)
+ SDL_RenderSetLogicalSize(font->renderer, prev_logicalw, prev_logicalh);
+ else {
+ SDL_RenderSetViewport(font->renderer, &prev_viewport);
+ SDL_RenderSetScale(font->renderer, prev_scalex, prev_scaley);
+ }
+ }
+ }
+#endif
+ return 1;
+}
+
+Uint8 FC_UploadGlyphCache(FC_Font* font, int cache_level, SDL_Surface* data_surface)
+{
+ if(font == NULL || data_surface == NULL)
+ return 0;
+ #ifdef FC_USE_SDL_GPU
+ GPU_Image* new_level = GPU_CopyImageFromSurface(data_surface);
+ GPU_SetAnchor(new_level, 0.5f, 0.5f); // Just in case the default is different
+ if(FC_GetFilterMode(font) == FC_FILTER_LINEAR)
+ GPU_SetImageFilter(new_level, GPU_FILTER_LINEAR);
+ else
+ GPU_SetImageFilter(new_level, GPU_FILTER_NEAREST);
+ #else
+ SDL_Texture* new_level;
+ if(!fc_has_render_target_support)
+ new_level = SDL_CreateTextureFromSurface(font->renderer, data_surface);
+ else
+ {
+ // Must upload with render target enabled so we can put more glyphs on later
+ SDL_Renderer* renderer = font->renderer;
+
+ // Set filter mode for new texture
+ char old_filter_mode[16]; // Save it so we can change the hint value in the meantime
+ snprintf(old_filter_mode, 16, "%s", SDL_GetHint(SDL_HINT_RENDER_SCALE_QUALITY));
+
+ if(FC_GetFilterMode(font) == FC_FILTER_LINEAR)
+ SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1");
+ else
+ SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "0");
+
+ new_level = SDL_CreateTexture(renderer, data_surface->format->format, SDL_TEXTUREACCESS_TARGET, data_surface->w, data_surface->h);
+ SDL_SetTextureBlendMode(new_level, SDL_BLENDMODE_BLEND);
+
+ // Reset filter mode for the temp texture
+ SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "0");
+
+ {
+ Uint8 r, g, b, a;
+ SDL_Texture* temp = SDL_CreateTextureFromSurface(renderer, data_surface);
+ SDL_Texture* prev_target = SDL_GetRenderTarget(renderer);
+ SDL_Rect prev_clip, prev_viewport;
+ int prev_logicalw, prev_logicalh;
+ Uint8 prev_clip_enabled;
+ float prev_scalex, prev_scaley;
+ // only backup if previous target existed (SDL will preserve them for the default target)
+ if (prev_target) {
+ prev_clip_enabled = has_clip(renderer);
+ if (prev_clip_enabled)
+ prev_clip = get_clip(renderer);
+ SDL_RenderGetViewport(renderer, &prev_viewport);
+ SDL_RenderGetScale(renderer, &prev_scalex, &prev_scaley);
+ SDL_RenderGetLogicalSize(renderer, &prev_logicalw, &prev_logicalh);
+ }
+ SDL_SetTextureBlendMode(temp, SDL_BLENDMODE_NONE);
+ SDL_SetRenderTarget(renderer, new_level);
+
+ SDL_GetRenderDrawColor(renderer, &r, &g, &b, &a);
+ SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0);
+ SDL_RenderClear(renderer);
+ SDL_SetRenderDrawColor(renderer, r, g, b, a);
+
+ SDL_RenderCopy(renderer, temp, NULL, NULL);
+ SDL_SetRenderTarget(renderer, prev_target);
+ if (prev_target) {
+ if (prev_clip_enabled)
+ set_clip(renderer, &prev_clip);
+ if (prev_logicalw && prev_logicalh)
+ SDL_RenderSetLogicalSize(renderer, prev_logicalw, prev_logicalh);
+ else {
+ SDL_RenderSetViewport(renderer, &prev_viewport);
+ SDL_RenderSetScale(renderer, prev_scalex, prev_scaley);
+ }
+ }
+
+ SDL_DestroyTexture(temp);
+ }
+
+ // Reset to the old filter value
+ SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, old_filter_mode);
+
+ }
+ #endif
+ if(new_level == NULL || !FC_SetGlyphCacheLevel(font, cache_level, new_level))
+ {
+ FC_Log("Error: SDL_FontCache ran out of packing space and could not add another cache level.\n");
+ #ifdef FC_USE_SDL_GPU
+ GPU_FreeImage(new_level);
+ #else
+ SDL_DestroyTexture(new_level);
+ #endif
+ return 0;
+ }
+ return 1;
+}
+
+static FC_GlyphData* FC_PackGlyphData(FC_Font* font, Uint32 codepoint, Uint16 width, Uint16 maxWidth, Uint16 maxHeight)
+{
+ FC_Map* glyphs = font->glyphs;
+ FC_GlyphData* last_glyph = &font->last_glyph;
+ Uint16 height = font->height + FC_CACHE_PADDING;
+
+ if(last_glyph->rect.x + last_glyph->rect.w + width >= maxWidth - FC_CACHE_PADDING)
+ {
+ if(last_glyph->rect.y + height + height >= maxHeight - FC_CACHE_PADDING)
+ {
+ // Get ready to pack on the next cache level when it is ready
+ last_glyph->cache_level = font->glyph_cache_count;
+ last_glyph->rect.x = FC_CACHE_PADDING;
+ last_glyph->rect.y = FC_CACHE_PADDING;
+ last_glyph->rect.w = 0;
+ return NULL;
+ }
+ else
+ {
+ // Go to next row
+ last_glyph->rect.x = FC_CACHE_PADDING;
+ last_glyph->rect.y += height;
+ last_glyph->rect.w = 0;
+ }
+ }
+
+ // Move to next space
+ last_glyph->rect.x += last_glyph->rect.w + 1 + FC_CACHE_PADDING;
+ last_glyph->rect.w = width;
+
+ return FC_MapInsert(glyphs, codepoint, FC_MakeGlyphData(last_glyph->cache_level, last_glyph->rect.x, last_glyph->rect.y, last_glyph->rect.w, last_glyph->rect.h));
+}
+
+
+FC_Image* FC_GetGlyphCacheLevel(FC_Font* font, int cache_level)
+{
+ if(font == NULL || cache_level < 0 || cache_level > font->glyph_cache_count)
+ return NULL;
+
+ return font->glyph_cache[cache_level];
+}
+
+Uint8 FC_SetGlyphCacheLevel(FC_Font* font, int cache_level, FC_Image* cache_texture)
+{
+ if(font == NULL || cache_level < 0)
+ return 0;
+
+ // Must be sequentially added
+ if(cache_level > font->glyph_cache_count + 1)
+ return 0;
+
+ if(cache_level == font->glyph_cache_count)
+ {
+ font->glyph_cache_count++;
+
+ // Grow cache?
+ if(font->glyph_cache_count > font->glyph_cache_size)
+ {
+ // Copy old cache to new one
+ int i;
+ FC_Image** new_cache;
+ new_cache = (FC_Image**)malloc(font->glyph_cache_count * sizeof(FC_Image*));
+ for(i = 0; i < font->glyph_cache_size; ++i)
+ new_cache[i] = font->glyph_cache[i];
+
+ // Save new cache
+ free(font->glyph_cache);
+ font->glyph_cache_size = font->glyph_cache_count;
+ font->glyph_cache = new_cache;
+ }
+ }
+
+ font->glyph_cache[cache_level] = cache_texture;
+ return 1;
+}
+
+
+FC_Font* FC_CreateFont(void)
+{
+ FC_Font* font;
+
+ font = (FC_Font*)malloc(sizeof(FC_Font));
+ memset(font, 0, sizeof(FC_Font));
+
+ FC_Init(font);
+
+ return font;
+}
+
+
+// Assume this many will be enough...
+#define FC_LOAD_MAX_SURFACES 10
+
+#ifdef FC_USE_SDL_GPU
+Uint8 FC_LoadFontFromTTF(FC_Font* font, TTF_Font* ttf, SDL_Color color)
+#else
+Uint8 FC_LoadFontFromTTF(FC_Font* font, SDL_Renderer* renderer, TTF_Font* ttf, SDL_Color color)
+#endif
+{
+ if(font == NULL || ttf == NULL)
+ return 0;
+ #ifndef FC_USE_SDL_GPU
+ if(renderer == NULL)
+ return 0;
+ #endif
+
+ FC_ClearFont(font);
+
+
+ // Might as well check render target support here
+ #ifdef FC_USE_SDL_GPU
+ fc_has_render_target_support = GPU_IsFeatureEnabled(GPU_FEATURE_RENDER_TARGETS);
+ #else
+ SDL_RendererInfo info;
+ SDL_GetRendererInfo(renderer, &info);
+ fc_has_render_target_support = (info.flags & SDL_RENDERER_TARGETTEXTURE);
+
+ font->renderer = renderer;
+ #endif
+
+ font->ttf_source = ttf;
+
+ //font->line_height = TTF_FontLineSkip(ttf);
+ font->height = TTF_FontHeight(ttf);
+ font->ascent = TTF_FontAscent(ttf);
+ font->descent = -TTF_FontDescent(ttf);
+
+ // Some bug for certain fonts can result in an incorrect height.
+ if(font->height < font->ascent - font->descent)
+ font->height = font->ascent - font->descent;
+
+ font->baseline = font->height - font->descent;
+
+ font->default_color = color;
+
+ {
+ SDL_Color white = {255, 255, 255, 255};
+ SDL_Surface* glyph_surf;
+ char buff[5];
+ const char* buff_ptr = buff;
+ const char* source_string;
+ Uint8 packed = 0;
+
+ // Copy glyphs from the surface to the font texture and store the position data
+ // Pack row by row into a square texture
+ // Try figuring out dimensions that make sense for the font size.
+ unsigned int w = font->height*12;
+ unsigned int h = font->height*12;
+ SDL_Surface* surfaces[FC_LOAD_MAX_SURFACES];
+ int num_surfaces = 1;
+ surfaces[0] = FC_CreateSurface32(w, h);
+ font->last_glyph.rect.x = FC_CACHE_PADDING;
+ font->last_glyph.rect.y = FC_CACHE_PADDING;
+ font->last_glyph.rect.w = 0;
+ font->last_glyph.rect.h = font->height;
+
+ source_string = font->loading_string;
+ for(; *source_string != '\0'; source_string = U8_next(source_string))
+ {
+ memset(buff, 0, 5);
+ if(!U8_charcpy(buff, source_string, 5))
+ continue;
+ glyph_surf = TTF_RenderText_Blended(ttf, buff, white);
+ if(glyph_surf == NULL)
+ continue;
+
+ // Try packing. If it fails, create a new surface for the next cache level.
+ packed = (FC_PackGlyphData(font, FC_GetCodepointFromUTF8(&buff_ptr, 0), glyph_surf->w, surfaces[num_surfaces-1]->w, surfaces[num_surfaces-1]->h) != NULL);
+ if(!packed)
+ {
+ int i = num_surfaces-1;
+ if(num_surfaces >= FC_LOAD_MAX_SURFACES)
+ {
+ // Can't do any more!
+ FC_Log("SDL_FontCache error: Could not create enough cache surfaces to fit all of the loading string!\n");
+ SDL_FreeSurface(glyph_surf);
+ break;
+ }
+
+ // Upload the current surface to the glyph cache now so we can keep the cache level packing cursor up to date as we go.
+ FC_UploadGlyphCache(font, i, surfaces[i]);
+ SDL_FreeSurface(surfaces[i]);
+ #ifndef FC_USE_SDL_GPU
+ SDL_SetTextureBlendMode(font->glyph_cache[i], SDL_BLENDMODE_BLEND);
+ #endif
+ // Update the glyph cursor to the new cache level. We need to do this here because the actual cache lags behind our use of the packing above.
+ font->last_glyph.cache_level = num_surfaces;
+
+
+ surfaces[num_surfaces] = FC_CreateSurface32(w, h);
+ num_surfaces++;
+ }
+
+ // Try packing for the new surface, then blit onto it.
+ if(packed || FC_PackGlyphData(font, FC_GetCodepointFromUTF8(&buff_ptr, 0), glyph_surf->w, surfaces[num_surfaces-1]->w, surfaces[num_surfaces-1]->h) != NULL)
+ {
+ SDL_SetSurfaceBlendMode(glyph_surf, SDL_BLENDMODE_NONE);
+ SDL_Rect srcRect = {0, 0, glyph_surf->w, glyph_surf->h};
+ SDL_Rect destrect = font->last_glyph.rect;
+ SDL_BlitSurface(glyph_surf, &srcRect, surfaces[num_surfaces-1], &destrect);
+ }
+
+ SDL_FreeSurface(glyph_surf);
+ }
+
+ {
+ int i = num_surfaces-1;
+ FC_UploadGlyphCache(font, i, surfaces[i]);
+ SDL_FreeSurface(surfaces[i]);
+ #ifndef FC_USE_SDL_GPU
+ SDL_SetTextureBlendMode(font->glyph_cache[i], SDL_BLENDMODE_BLEND);
+ #endif
+ }
+ }
+
+ return 1;
+}
+
+
+#ifdef FC_USE_SDL_GPU
+Uint8 FC_LoadFont(FC_Font* font, const char* filename_ttf, Uint32 pointSize, SDL_Color color, int style)
+#else
+Uint8 FC_LoadFont(FC_Font* font, FC_Target* renderer, const char* filename_ttf, Uint32 pointSize, SDL_Color color, int style)
+#endif
+{
+ SDL_RWops* rwops;
+
+ if(font == NULL)
+ return 0;
+
+ rwops = SDL_RWFromFile(filename_ttf, "rb");
+
+ if(rwops == NULL)
+ {
+ FC_Log("Unable to open file for reading: %s \n", SDL_GetError());
+ return 0;
+ }
+
+ #ifdef FC_USE_SDL_GPU
+ return FC_LoadFont_RW(font, rwops, 1, pointSize, color, style);
+ #else
+ return FC_LoadFont_RW(font, renderer, rwops, 1, pointSize, color, style);
+ #endif
+}
+
+#ifdef FC_USE_SDL_GPU
+Uint8 FC_LoadFont_RW(FC_Font* font, SDL_RWops* file_rwops_ttf, Uint8 own_rwops, Uint32 pointSize, SDL_Color color, int style)
+#else
+Uint8 FC_LoadFont_RW(FC_Font* font, FC_Target* renderer, SDL_RWops* file_rwops_ttf, Uint8 own_rwops, Uint32 pointSize, SDL_Color color, int style)
+#endif
+{
+ Uint8 result;
+ TTF_Font* ttf;
+ Uint8 outline;
+
+ if(font == NULL)
+ return 0;
+
+ if(!TTF_WasInit() && TTF_Init() < 0)
+ {
+ FC_Log("Unable to initialize SDL_ttf: %s \n", TTF_GetError());
+ if(own_rwops)
+ SDL_RWclose(file_rwops_ttf);
+ return 0;
+ }
+
+ ttf = TTF_OpenFontRW(file_rwops_ttf, own_rwops, pointSize);
+
+ if(ttf == NULL)
+ {
+ FC_Log("Unable to load TrueType font: %s \n", TTF_GetError());
+ if(own_rwops)
+ SDL_RWclose(file_rwops_ttf);
+ return 0;
+ }
+
+ outline = (style & TTF_STYLE_OUTLINE);
+ if(outline)
+ {
+ style &= ~TTF_STYLE_OUTLINE;
+ TTF_SetFontOutline(ttf, 1);
+ }
+ TTF_SetFontStyle(ttf, style);
+
+ #ifdef FC_USE_SDL_GPU
+ result = FC_LoadFontFromTTF(font, ttf, color);
+ #else
+ result = FC_LoadFontFromTTF(font, renderer, ttf, color);
+ #endif
+
+ // Can only load new (uncached) glyphs if we can keep the SDL_RWops open.
+ font->owns_ttf_source = own_rwops;
+ if(!own_rwops)
+ {
+ TTF_CloseFont(font->ttf_source);
+ font->ttf_source = NULL;
+ }
+
+ return result;
+}
+
+
+#ifndef FC_USE_SDL_GPU
+void FC_ResetFontFromRendererReset(FC_Font* font, SDL_Renderer* renderer, Uint32 evType)
+{
+ TTF_Font* ttf;
+ SDL_Color col;
+ Uint8 owns_ttf;
+ if (font == NULL)
+ return;
+
+ // Destroy glyph cache
+ if (evType == SDL_RENDER_TARGETS_RESET) {
+ int i;
+ for (i = 0; i < font->glyph_cache_count; ++i)
+ SDL_DestroyTexture(font->glyph_cache[i]);
+ }
+ free(font->glyph_cache);
+
+ ttf = font->ttf_source;
+ col = font->default_color;
+ owns_ttf = font->owns_ttf_source;
+ FC_Init(font);
+
+ // Can only reload glyphs if we own the SDL_RWops.
+ if (owns_ttf)
+ FC_LoadFontFromTTF(font, renderer, ttf, col);
+ font->owns_ttf_source = owns_ttf;
+}
+#endif
+
+void FC_ClearFont(FC_Font* font)
+{
+ int i;
+ if(font == NULL)
+ return;
+
+ // Release resources
+ if(font->owns_ttf_source)
+ TTF_CloseFont(font->ttf_source);
+
+ font->owns_ttf_source = 0;
+ font->ttf_source = NULL;
+
+ // Delete glyph map
+ FC_MapFree(font->glyphs);
+ font->glyphs = NULL;
+
+ // Delete glyph cache
+ for(i = 0; i < font->glyph_cache_count; ++i)
+ {
+ #ifdef FC_USE_SDL_GPU
+ GPU_FreeImage(font->glyph_cache[i]);
+ #else
+ SDL_DestroyTexture(font->glyph_cache[i]);
+ #endif
+ }
+ free(font->glyph_cache);
+ font->glyph_cache = NULL;
+
+ // Reset font
+ FC_Init(font);
+}
+
+
+void FC_FreeFont(FC_Font* font)
+{
+ int i;
+ if(font == NULL)
+ return;
+
+ // Release resources
+ if(font->owns_ttf_source)
+ TTF_CloseFont(font->ttf_source);
+
+ // Delete glyph map
+ FC_MapFree(font->glyphs);
+
+ // Delete glyph cache
+ for(i = 0; i < font->glyph_cache_count; ++i)
+ {
+ #ifdef FC_USE_SDL_GPU
+ GPU_FreeImage(font->glyph_cache[i]);
+ #else
+ SDL_DestroyTexture(font->glyph_cache[i]);
+ #endif
+ }
+ free(font->glyph_cache);
+
+ free(font->loading_string);
+
+ free(font);
+}
+
+int FC_GetNumCacheLevels(FC_Font* font)
+{
+ return font->glyph_cache_count;
+}
+
+Uint8 FC_AddGlyphToCache(FC_Font* font, SDL_Surface* glyph_surface)
+{
+ if(font == NULL || glyph_surface == NULL)
+ return 0;
+
+ SDL_SetSurfaceBlendMode(glyph_surface, SDL_BLENDMODE_NONE);
+ FC_Image* dest = FC_GetGlyphCacheLevel(font, font->last_glyph.cache_level);
+ if(dest == NULL)
+ return 0;
+
+ #ifdef FC_USE_SDL_GPU
+ {
+ GPU_Target* target = GPU_LoadTarget(dest);
+ if(target == NULL)
+ return 0;
+ GPU_Image* img = GPU_CopyImageFromSurface(glyph_surface);
+ GPU_SetAnchor(img, 0.5f, 0.5f); // Just in case the default is different
+ GPU_SetImageFilter(img, GPU_FILTER_NEAREST);
+ GPU_SetBlendMode(img, GPU_BLEND_SET);
+
+ SDL_Rect destrect = font->last_glyph.rect;
+ GPU_Blit(img, NULL, target, destrect.x + destrect.w/2, destrect.y + destrect.h/2);
+
+ GPU_FreeImage(img);
+ GPU_FreeTarget(target);
+ }
+ #else
+ {
+ SDL_Renderer* renderer = font->renderer;
+ SDL_Texture* img;
+ SDL_Rect destrect;
+ SDL_Texture* prev_target = SDL_GetRenderTarget(renderer);
+ SDL_Rect prev_clip, prev_viewport;
+ int prev_logicalw, prev_logicalh;
+ Uint8 prev_clip_enabled;
+ float prev_scalex, prev_scaley;
+ // only backup if previous target existed (SDL will preserve them for the default target)
+ if (prev_target) {
+ prev_clip_enabled = has_clip(renderer);
+ if (prev_clip_enabled)
+ prev_clip = get_clip(renderer);
+ SDL_RenderGetViewport(renderer, &prev_viewport);
+ SDL_RenderGetScale(renderer, &prev_scalex, &prev_scaley);
+ SDL_RenderGetLogicalSize(renderer, &prev_logicalw, &prev_logicalh);
+ }
+
+ img = SDL_CreateTextureFromSurface(renderer, glyph_surface);
+
+ destrect = font->last_glyph.rect;
+ SDL_SetRenderTarget(renderer, dest);
+ SDL_RenderCopy(renderer, img, NULL, &destrect);
+ SDL_SetRenderTarget(renderer, prev_target);
+ if (prev_target) {
+ if (prev_clip_enabled)
+ set_clip(renderer, &prev_clip);
+ if (prev_logicalw && prev_logicalh)
+ SDL_RenderSetLogicalSize(renderer, prev_logicalw, prev_logicalh);
+ else {
+ SDL_RenderSetViewport(renderer, &prev_viewport);
+ SDL_RenderSetScale(renderer, prev_scalex, prev_scaley);
+ }
+ }
+
+ SDL_DestroyTexture(img);
+ }
+ #endif
+
+ return 1;
+}
+
+
+unsigned int FC_GetNumCodepoints(FC_Font* font)
+{
+ FC_Map* glyphs;
+ int i;
+ unsigned int result = 0;
+ if(font == NULL || font->glyphs == NULL)
+ return 0;
+
+ glyphs = font->glyphs;
+
+ for(i = 0; i < glyphs->num_buckets; ++i)
+ {
+ FC_MapNode* node;
+ for(node = glyphs->buckets[i]; node != NULL; node = node->next)
+ {
+ result++;
+ }
+ }
+
+ return result;
+}
+
+void FC_GetCodepoints(FC_Font* font, Uint32* result)
+{
+ FC_Map* glyphs;
+ int i;
+ unsigned int count = 0;
+ if(font == NULL || font->glyphs == NULL)
+ return;
+
+ glyphs = font->glyphs;
+
+ for(i = 0; i < glyphs->num_buckets; ++i)
+ {
+ FC_MapNode* node;
+ for(node = glyphs->buckets[i]; node != NULL; node = node->next)
+ {
+ result[count] = node->key;
+ count++;
+ }
+ }
+}
+
+Uint8 FC_GetGlyphData(FC_Font* font, FC_GlyphData* result, Uint32 codepoint)
+{
+ FC_GlyphData* e = FC_MapFind(font->glyphs, codepoint);
+ if(e == NULL)
+ {
+ char buff[5];
+ int w, h;
+ SDL_Color white = {255, 255, 255, 255};
+ SDL_Surface* surf;
+ FC_Image* cache_image;
+
+ if(font->ttf_source == NULL)
+ return 0;
+
+ FC_GetUTF8FromCodepoint(buff, codepoint);
+
+ cache_image = FC_GetGlyphCacheLevel(font, font->last_glyph.cache_level);
+ if(cache_image == NULL)
+ {
+ FC_Log("SDL_FontCache: Failed to load cache image, so cannot add new glyphs!\n");
+ return 0;
+ }
+
+ #ifdef FC_USE_SDL_GPU
+ w = cache_image->w;
+ h = cache_image->h;
+ #else
+ SDL_QueryTexture(cache_image, NULL, NULL, &w, &h);
+ #endif
+
+ surf = TTF_RenderText_Blended(font->ttf_source, buff, white);
+ if(surf == NULL)
+ {
+ return 0;
+ }
+
+ e = FC_PackGlyphData(font, codepoint, surf->w, w, h);
+ if(e == NULL)
+ {
+ // Grow the cache
+ FC_GrowGlyphCache(font);
+
+ // Try packing again
+ e = FC_PackGlyphData(font, codepoint, surf->w, w, h);
+ if(e == NULL)
+ {
+ SDL_FreeSurface(surf);
+ return 0;
+ }
+ }
+
+ // Render onto the cache texture
+ FC_AddGlyphToCache(font, surf);
+
+ SDL_FreeSurface(surf);
+ }
+
+ if(result != NULL && e != NULL)
+ *result = *e;
+
+ return 1;
+}
+
+
+FC_GlyphData* FC_SetGlyphData(FC_Font* font, Uint32 codepoint, FC_GlyphData glyph_data)
+{
+ return FC_MapInsert(font->glyphs, codepoint, glyph_data);
+}
+
+
+
+// Drawing
+static FC_Rect FC_RenderLeft(FC_Font* font, FC_Target* dest, float x, float y, FC_Scale scale, const char* text)
+{
+ const char* c = text;
+ FC_Rect srcRect;
+ FC_Rect dstRect;
+ FC_Rect dirtyRect = FC_MakeRect(x, y, 0, 0);
+
+ FC_GlyphData glyph;
+ Uint32 codepoint;
+
+ float destX = x;
+ float destY = y;
+ float destH;
+ float destLineSpacing;
+ float destLetterSpacing;
+
+ if(font == NULL)
+ return dirtyRect;
+
+ destH = font->height * scale.y;
+ destLineSpacing = font->lineSpacing*scale.y;
+ destLetterSpacing = font->letterSpacing*scale.x;
+
+ if(c == NULL || font->glyph_cache_count == 0 || dest == NULL)
+ return dirtyRect;
+
+ int newlineX = x;
+
+ for(; *c != '\0'; c++)
+ {
+ if(*c == '\n')
+ {
+ destX = newlineX;
+ destY += destH + destLineSpacing;
+ continue;
+ }
+
+ codepoint = FC_GetCodepointFromUTF8(&c, 1); // Increments 'c' to skip the extra UTF-8 bytes
+ if(!FC_GetGlyphData(font, &glyph, codepoint))
+ {
+ codepoint = ' ';
+ if(!FC_GetGlyphData(font, &glyph, codepoint))
+ continue; // Skip bad characters
+ }
+
+ if (codepoint == ' ')
+ {
+ destX += glyph.rect.w*scale.x + destLetterSpacing;
+ continue;
+ }
+ /*if(destX >= dest->w)
+ continue;
+ if(destY >= dest->h)
+ continue;*/
+
+ #ifdef FC_USE_SDL_GPU
+ srcRect.x = glyph.rect.x;
+ srcRect.y = glyph.rect.y;
+ srcRect.w = glyph.rect.w;
+ srcRect.h = glyph.rect.h;
+ #else
+ srcRect = glyph.rect;
+ #endif
+ dstRect = fc_render_callback(FC_GetGlyphCacheLevel(font, glyph.cache_level), &srcRect, dest, destX, destY, scale.x, scale.y);
+ if(dirtyRect.w == 0 || dirtyRect.h == 0)
+ dirtyRect = dstRect;
+ else
+ dirtyRect = FC_RectUnion(dirtyRect, dstRect);
+
+ destX += glyph.rect.w*scale.x + destLetterSpacing;
+ }
+
+ return dirtyRect;
+}
+
+static void set_color_for_all_caches(FC_Font* font, SDL_Color color)
+{
+ // TODO: How can I predict which glyph caches are to be used?
+ FC_Image* img;
+ int i;
+ int num_levels = FC_GetNumCacheLevels(font);
+ for(i = 0; i < num_levels; ++i)
+ {
+ img = FC_GetGlyphCacheLevel(font, i);
+ set_color(img, color.r, color.g, color.b, FC_GET_ALPHA(color));
+ }
+}
+
+FC_Rect FC_Draw(FC_Font* font, FC_Target* dest, float x, float y, const char* formatted_text, ...)
+{
+ if(formatted_text == NULL || font == NULL)
+ return FC_MakeRect(x, y, 0, 0);
+
+ FC_EXTRACT_VARARGS(fc_buffer, formatted_text);
+
+ set_color_for_all_caches(font, font->default_color);
+
+ return FC_RenderLeft(font, dest, x, y, FC_MakeScale(1,1), fc_buffer);
+}
+
+
+
+typedef struct FC_StringList
+{
+ char* value;
+ struct FC_StringList* next;
+} FC_StringList;
+
+void FC_StringListFree(FC_StringList* node)
+{
+ // Delete the nodes in order
+ while(node != NULL)
+ {
+ FC_StringList* last = node;
+ node = node->next;
+
+ free(last->value);
+ free(last);
+ }
+}
+
+FC_StringList** FC_StringListPushBack(FC_StringList** node, char* value, Uint8 copy)
+{
+ if(node == NULL)
+ {
+ return node;
+ }
+
+ // Get to the last node
+ while(*node != NULL)
+ {
+ node = &(*node)->next;
+ }
+
+ *node = (FC_StringList*)malloc(sizeof(FC_StringList));
+
+ (*node)->value = (copy? U8_strdup(value) : value);
+ (*node)->next = NULL;
+
+ return node;
+}
+
+static FC_StringList* FC_Explode(const char* text, char delimiter)
+{
+ FC_StringList* head;
+ FC_StringList* new_node;
+ FC_StringList** node;
+ const char* start;
+ const char* end;
+ unsigned int size;
+ if(text == NULL)
+ return NULL;
+
+ head = NULL;
+ node = &head;
+
+ // Doesn't technically support UTF-8, but it's probably fine, right?
+ size = 0;
+ start = end = text;
+ while(1)
+ {
+ if(*end == delimiter || *end == '\0')
+ {
+ *node = (FC_StringList*)malloc(sizeof(FC_StringList));
+ new_node = *node;
+
+ new_node->value = (char*)malloc(size + 1);
+ memcpy(new_node->value, start, size);
+ new_node->value[size] = '\0';
+
+ new_node->next = NULL;
+
+ if(*end == '\0')
+ break;
+
+ node = &((*node)->next);
+ start = end+1;
+ size = 0;
+ }
+ else
+ ++size;
+
+ ++end;
+ }
+
+ return head;
+}
+
+static FC_StringList* FC_ExplodeAndKeep(const char* text, char delimiter)
+{
+ FC_StringList* head;
+ FC_StringList* new_node;
+ FC_StringList** node;
+ const char* start;
+ const char* end;
+ unsigned int size;
+ if(text == NULL)
+ return NULL;
+
+ head = NULL;
+ node = &head;
+
+ // Doesn't technically support UTF-8, but it's probably fine, right?
+ size = 0;
+ start = end = text;
+ while(1)
+ {
+ if(*end == delimiter || *end == '\0')
+ {
+ *node = (FC_StringList*)malloc(sizeof(FC_StringList));
+ new_node = *node;
+
+ new_node->value = (char*)malloc(size + 1);
+ memcpy(new_node->value, start, size);
+ new_node->value[size] = '\0';
+
+ new_node->next = NULL;
+
+ if(*end == '\0')
+ break;
+
+ node = &((*node)->next);
+ start = end;
+ size = 1;
+ }
+ else
+ ++size;
+
+ ++end;
+ }
+
+ return head;
+}
+
+static void FC_RenderAlign(FC_Font* font, FC_Target* dest, float x, float y, int width, FC_Scale scale, FC_AlignEnum align, const char* text)
+{
+ switch(align)
+ {
+ case FC_ALIGN_LEFT:
+ FC_RenderLeft(font, dest, x, y, scale, text);
+ break;
+ case FC_ALIGN_CENTER:
+ FC_RenderCenter(font, dest, x + width/2, y, scale, text);
+ break;
+ case FC_ALIGN_RIGHT:
+ FC_RenderRight(font, dest, x + width, y, scale, text);
+ break;
+ }
+}
+
+static FC_StringList* FC_GetBufferFitToColumn(FC_Font* font, int width, FC_Scale scale, Uint8 keep_newlines)
+{
+ FC_StringList* result = NULL;
+ FC_StringList** current = &result;
+
+ FC_StringList *ls, *iter;
+
+ ls = (keep_newlines? FC_ExplodeAndKeep(fc_buffer, '\n') : FC_Explode(fc_buffer, '\n'));
+ for(iter = ls; iter != NULL; iter = iter->next)
+ {
+ char* line = iter->value;
+
+ // If line is too long, then add words one at a time until we go over.
+ if(width > 0 && FC_GetWidth(font, "%s", line) > width)
+ {
+ FC_StringList *words, *word_iter;
+
+ words = FC_Explode(line, ' ');
+ // Skip the first word for the iterator, so there will always be at least one word per line
+ line = new_concat(words->value, " ");
+ for(word_iter = words->next; word_iter != NULL; word_iter = word_iter->next)
+ {
+ char* line_plus_word = new_concat(line, word_iter->value);
+ char* word_plus_space = new_concat(word_iter->value, " ");
+ if(FC_GetWidth(font, "%s", line_plus_word) > width)
+ {
+ current = FC_StringListPushBack(current, line, 0);
+
+ line = word_plus_space;
+ }
+ else
+ {
+ replace_concat(&line, word_plus_space);
+ free(word_plus_space);
+ }
+ free(line_plus_word);
+ }
+ current = FC_StringListPushBack(current, line, 0);
+ FC_StringListFree(words);
+ }
+ else
+ {
+ current = FC_StringListPushBack(current, line, 0);
+ iter->value = NULL;
+ }
+ }
+ FC_StringListFree(ls);
+
+ return result;
+}
+
+static void FC_DrawColumnFromBuffer(FC_Font* font, FC_Target* dest, FC_Rect box, int* total_height, FC_Scale scale, FC_AlignEnum align)
+{
+ int y = box.y;
+ FC_StringList *ls, *iter;
+
+ ls = FC_GetBufferFitToColumn(font, box.w, scale, 0);
+ for(iter = ls; iter != NULL; iter = iter->next)
+ {
+ FC_RenderAlign(font, dest, box.x, y, box.w, scale, align, iter->value);
+ y += FC_GetLineHeight(font);
+ }
+ FC_StringListFree(ls);
+
+ if(total_height != NULL)
+ *total_height = y - box.y;
+}
+
+FC_Rect FC_DrawBox(FC_Font* font, FC_Target* dest, FC_Rect box, const char* formatted_text, ...)
+{
+ Uint8 useClip;
+ if(formatted_text == NULL || font == NULL)
+ return FC_MakeRect(box.x, box.y, 0, 0);
+
+ FC_EXTRACT_VARARGS(fc_buffer, formatted_text);
+
+ useClip = has_clip(dest);
+ FC_Rect oldclip, newclip;
+ if(useClip)
+ {
+ oldclip = get_clip(dest);
+ newclip = FC_RectIntersect(oldclip, box);
+ }
+ else
+ newclip = box;
+
+ set_clip(dest, &newclip);
+
+ set_color_for_all_caches(font, font->default_color);
+
+ FC_DrawColumnFromBuffer(font, dest, box, NULL, FC_MakeScale(1,1), FC_ALIGN_LEFT);
+
+ if(useClip)
+ set_clip(dest, &oldclip);
+ else
+ set_clip(dest, NULL);
+
+ return box;
+}
+
+FC_Rect FC_DrawBoxAlign(FC_Font* font, FC_Target* dest, FC_Rect box, FC_AlignEnum align, const char* formatted_text, ...)
+{
+ Uint8 useClip;
+ if(formatted_text == NULL || font == NULL)
+ return FC_MakeRect(box.x, box.y, 0, 0);
+
+ FC_EXTRACT_VARARGS(fc_buffer, formatted_text);
+
+ useClip = has_clip(dest);
+ FC_Rect oldclip, newclip;
+ if(useClip)
+ {
+ oldclip = get_clip(dest);
+ newclip = FC_RectIntersect(oldclip, box);
+ }
+ else
+ newclip = box;
+ set_clip(dest, &newclip);
+
+ set_color_for_all_caches(font, font->default_color);
+
+ FC_DrawColumnFromBuffer(font, dest, box, NULL, FC_MakeScale(1,1), align);
+
+ if(useClip)
+ set_clip(dest, &oldclip);
+ else
+ set_clip(dest, NULL);
+
+ return box;
+}
+
+FC_Rect FC_DrawBoxScale(FC_Font* font, FC_Target* dest, FC_Rect box, FC_Scale scale, const char* formatted_text, ...)
+{
+ Uint8 useClip;
+ if(formatted_text == NULL || font == NULL)
+ return FC_MakeRect(box.x, box.y, 0, 0);
+
+ FC_EXTRACT_VARARGS(fc_buffer, formatted_text);
+
+ useClip = has_clip(dest);
+ FC_Rect oldclip, newclip;
+ if(useClip)
+ {
+ oldclip = get_clip(dest);
+ newclip = FC_RectIntersect(oldclip, box);
+ }
+ else
+ newclip = box;
+ set_clip(dest, &newclip);
+
+ set_color_for_all_caches(font, font->default_color);
+
+ FC_DrawColumnFromBuffer(font, dest, box, NULL, scale, FC_ALIGN_LEFT);
+
+ if(useClip)
+ set_clip(dest, &oldclip);
+ else
+ set_clip(dest, NULL);
+
+ return box;
+}
+
+FC_Rect FC_DrawBoxColor(FC_Font* font, FC_Target* dest, FC_Rect box, SDL_Color color, const char* formatted_text, ...)
+{
+ Uint8 useClip;
+ if(formatted_text == NULL || font == NULL)
+ return FC_MakeRect(box.x, box.y, 0, 0);
+
+ FC_EXTRACT_VARARGS(fc_buffer, formatted_text);
+
+ useClip = has_clip(dest);
+ FC_Rect oldclip, newclip;
+ if(useClip)
+ {
+ oldclip = get_clip(dest);
+ newclip = FC_RectIntersect(oldclip, box);
+ }
+ else
+ newclip = box;
+ set_clip(dest, &newclip);
+
+ set_color_for_all_caches(font, color);
+
+ FC_DrawColumnFromBuffer(font, dest, box, NULL, FC_MakeScale(1,1), FC_ALIGN_LEFT);
+
+ if(useClip)
+ set_clip(dest, &oldclip);
+ else
+ set_clip(dest, NULL);
+
+ return box;
+}
+
+FC_Rect FC_DrawBoxEffect(FC_Font* font, FC_Target* dest, FC_Rect box, FC_Effect effect, const char* formatted_text, ...)
+{
+ Uint8 useClip;
+ if(formatted_text == NULL || font == NULL)
+ return FC_MakeRect(box.x, box.y, 0, 0);
+
+ FC_EXTRACT_VARARGS(fc_buffer, formatted_text);
+
+ useClip = has_clip(dest);
+ FC_Rect oldclip, newclip;
+ if(useClip)
+ {
+ oldclip = get_clip(dest);
+ newclip = FC_RectIntersect(oldclip, box);
+ }
+ else
+ newclip = box;
+ set_clip(dest, &newclip);
+
+ set_color_for_all_caches(font, effect.color);
+
+ FC_DrawColumnFromBuffer(font, dest, box, NULL, effect.scale, effect.alignment);
+
+ if(useClip)
+ set_clip(dest, &oldclip);
+ else
+ set_clip(dest, NULL);
+
+ return box;
+}
+
+FC_Rect FC_DrawColumn(FC_Font* font, FC_Target* dest, float x, float y, Uint16 width, const char* formatted_text, ...)
+{
+ FC_Rect box = {x, y, width, 0};
+ int total_height;
+
+ if(formatted_text == NULL || font == NULL)
+ return FC_MakeRect(x, y, 0, 0);
+
+ FC_EXTRACT_VARARGS(fc_buffer, formatted_text);
+
+ set_color_for_all_caches(font, font->default_color);
+
+ FC_DrawColumnFromBuffer(font, dest, box, &total_height, FC_MakeScale(1,1), FC_ALIGN_LEFT);
+
+ return FC_MakeRect(box.x, box.y, width, total_height);
+}
+
+FC_Rect FC_DrawColumnAlign(FC_Font* font, FC_Target* dest, float x, float y, Uint16 width, FC_AlignEnum align, const char* formatted_text, ...)
+{
+ FC_Rect box = {x, y, width, 0};
+ int total_height;
+
+ if(formatted_text == NULL || font == NULL)
+ return FC_MakeRect(x, y, 0, 0);
+
+ FC_EXTRACT_VARARGS(fc_buffer, formatted_text);
+
+ set_color_for_all_caches(font, font->default_color);
+
+ switch(align)
+ {
+ case FC_ALIGN_CENTER:
+ box.x -= width/2;
+ break;
+ case FC_ALIGN_RIGHT:
+ box.x -= width;
+ break;
+ default:
+ break;
+ }
+
+ FC_DrawColumnFromBuffer(font, dest, box, &total_height, FC_MakeScale(1,1), align);
+
+ return FC_MakeRect(box.x, box.y, width, total_height);
+}
+
+FC_Rect FC_DrawColumnScale(FC_Font* font, FC_Target* dest, float x, float y, Uint16 width, FC_Scale scale, const char* formatted_text, ...)
+{
+ FC_Rect box = {x, y, width, 0};
+ int total_height;
+
+ if(formatted_text == NULL || font == NULL)
+ return FC_MakeRect(x, y, 0, 0);
+
+ FC_EXTRACT_VARARGS(fc_buffer, formatted_text);
+
+ set_color_for_all_caches(font, font->default_color);
+
+ FC_DrawColumnFromBuffer(font, dest, box, &total_height, scale, FC_ALIGN_LEFT);
+
+ return FC_MakeRect(box.x, box.y, width, total_height);
+}
+
+FC_Rect FC_DrawColumnColor(FC_Font* font, FC_Target* dest, float x, float y, Uint16 width, SDL_Color color, const char* formatted_text, ...)
+{
+ FC_Rect box = {x, y, width, 0};
+ int total_height;
+
+ if(formatted_text == NULL || font == NULL)
+ return FC_MakeRect(x, y, 0, 0);
+
+ FC_EXTRACT_VARARGS(fc_buffer, formatted_text);
+
+ set_color_for_all_caches(font, color);
+
+ FC_DrawColumnFromBuffer(font, dest, box, &total_height, FC_MakeScale(1,1), FC_ALIGN_LEFT);
+
+ return FC_MakeRect(box.x, box.y, width, total_height);
+}
+
+FC_Rect FC_DrawColumnEffect(FC_Font* font, FC_Target* dest, float x, float y, Uint16 width, FC_Effect effect, const char* formatted_text, ...)
+{
+ FC_Rect box = {x, y, width, 0};
+ int total_height;
+
+ if(formatted_text == NULL || font == NULL)
+ return FC_MakeRect(x, y, 0, 0);
+
+ FC_EXTRACT_VARARGS(fc_buffer, formatted_text);
+
+ set_color_for_all_caches(font, effect.color);
+
+ switch(effect.alignment)
+ {
+ case FC_ALIGN_CENTER:
+ box.x -= width/2;
+ break;
+ case FC_ALIGN_RIGHT:
+ box.x -= width;
+ break;
+ default:
+ break;
+ }
+
+ FC_DrawColumnFromBuffer(font, dest, box, &total_height, effect.scale, effect.alignment);
+
+ return FC_MakeRect(box.x, box.y, width, total_height);
+}
+
+static FC_Rect FC_RenderCenter(FC_Font* font, FC_Target* dest, float x, float y, FC_Scale scale, const char* text)
+{
+ FC_Rect result = {x, y, 0, 0};
+ if(text == NULL || font == NULL)
+ return result;
+
+ char* str = U8_strdup(text);
+ char* del = str;
+ char* c;
+
+ // Go through str, when you find a \n, replace it with \0 and print it
+ // then move down, back, and continue.
+ for(c = str; *c != '\0';)
+ {
+ if(*c == '\n')
+ {
+ *c = '\0';
+ result = FC_RectUnion(FC_RenderLeft(font, dest, x - scale.x*FC_GetWidth(font, "%s", str)/2.0f, y, scale, str), result);
+ *c = '\n';
+ c++;
+ str = c;
+ y += scale.y*font->height;
+ }
+ else
+ c++;
+ }
+
+ result = FC_RectUnion(FC_RenderLeft(font, dest, x - scale.x*FC_GetWidth(font, "%s", str)/2.0f, y, scale, str), result);
+
+ free(del);
+ return result;
+}
+
+static FC_Rect FC_RenderRight(FC_Font* font, FC_Target* dest, float x, float y, FC_Scale scale, const char* text)
+{
+ FC_Rect result = {x, y, 0, 0};
+ if(text == NULL || font == NULL)
+ return result;
+
+ char* str = U8_strdup(text);
+ char* del = str;
+ char* c;
+
+ for(c = str; *c != '\0';)
+ {
+ if(*c == '\n')
+ {
+ *c = '\0';
+ result = FC_RectUnion(FC_RenderLeft(font, dest, x - scale.x*FC_GetWidth(font, "%s", str), y, scale, str), result);
+ *c = '\n';
+ c++;
+ str = c;
+ y += scale.y*font->height;
+ }
+ else
+ c++;
+ }
+
+ result = FC_RectUnion(FC_RenderLeft(font, dest, x - scale.x*FC_GetWidth(font, "%s", str), y, scale, str), result);
+
+ free(del);
+ return result;
+}
+
+
+
+FC_Rect FC_DrawScale(FC_Font* font, FC_Target* dest, float x, float y, FC_Scale scale, const char* formatted_text, ...)
+{
+ if(formatted_text == NULL || font == NULL)
+ return FC_MakeRect(x, y, 0, 0);
+
+ FC_EXTRACT_VARARGS(fc_buffer, formatted_text);
+
+ set_color_for_all_caches(font, font->default_color);
+
+ return FC_RenderLeft(font, dest, x, y, scale, fc_buffer);
+}
+
+FC_Rect FC_DrawAlign(FC_Font* font, FC_Target* dest, float x, float y, FC_AlignEnum align, const char* formatted_text, ...)
+{
+ if(formatted_text == NULL || font == NULL)
+ return FC_MakeRect(x, y, 0, 0);
+
+ FC_EXTRACT_VARARGS(fc_buffer, formatted_text);
+
+ set_color_for_all_caches(font, font->default_color);
+
+ FC_Rect result;
+ switch(align)
+ {
+ case FC_ALIGN_LEFT:
+ result = FC_RenderLeft(font, dest, x, y, FC_MakeScale(1,1), fc_buffer);
+ break;
+ case FC_ALIGN_CENTER:
+ result = FC_RenderCenter(font, dest, x, y, FC_MakeScale(1,1), fc_buffer);
+ break;
+ case FC_ALIGN_RIGHT:
+ result = FC_RenderRight(font, dest, x, y, FC_MakeScale(1,1), fc_buffer);
+ break;
+ default:
+ result = FC_MakeRect(x, y, 0, 0);
+ break;
+ }
+
+ return result;
+}
+
+FC_Rect FC_DrawColor(FC_Font* font, FC_Target* dest, float x, float y, SDL_Color color, const char* formatted_text, ...)
+{
+ if(formatted_text == NULL || font == NULL)
+ return FC_MakeRect(x, y, 0, 0);
+
+ FC_EXTRACT_VARARGS(fc_buffer, formatted_text);
+
+ set_color_for_all_caches(font, color);
+
+ return FC_RenderLeft(font, dest, x, y, FC_MakeScale(1,1), fc_buffer);
+}
+
+
+FC_Rect FC_DrawEffect(FC_Font* font, FC_Target* dest, float x, float y, FC_Effect effect, const char* formatted_text, ...)
+{
+ if(formatted_text == NULL || font == NULL)
+ return FC_MakeRect(x, y, 0, 0);
+
+ FC_EXTRACT_VARARGS(fc_buffer, formatted_text);
+
+ set_color_for_all_caches(font, effect.color);
+
+ FC_Rect result;
+ switch(effect.alignment)
+ {
+ case FC_ALIGN_LEFT:
+ result = FC_RenderLeft(font, dest, x, y, effect.scale, fc_buffer);
+ break;
+ case FC_ALIGN_CENTER:
+ result = FC_RenderCenter(font, dest, x, y, effect.scale, fc_buffer);
+ break;
+ case FC_ALIGN_RIGHT:
+ result = FC_RenderRight(font, dest, x, y, effect.scale, fc_buffer);
+ break;
+ default:
+ result = FC_MakeRect(x, y, 0, 0);
+ break;
+ }
+
+ return result;
+}
+
+
+
+
+// Getters
+
+
+FC_FilterEnum FC_GetFilterMode(FC_Font* font)
+{
+ if(font == NULL)
+ return FC_FILTER_NEAREST;
+
+ return font->filter;
+}
+
+Uint16 FC_GetLineHeight(FC_Font* font)
+{
+ if(font == NULL)
+ return 0;
+
+ return font->height;
+}
+
+Uint16 FC_GetHeight(FC_Font* font, const char* formatted_text, ...)
+{
+ if(formatted_text == NULL || font == NULL)
+ return 0;
+
+ FC_EXTRACT_VARARGS(fc_buffer, formatted_text);
+
+ Uint16 numLines = 1;
+ const char* c;
+
+ for (c = fc_buffer; *c != '\0'; c++)
+ {
+ if(*c == '\n')
+ numLines++;
+ }
+
+ // Actual height of letter region + line spacing
+ return font->height*numLines + font->lineSpacing*(numLines - 1); //height*numLines;
+}
+
+Uint16 FC_GetWidth(FC_Font* font, const char* formatted_text, ...)
+{
+ if(formatted_text == NULL || font == NULL)
+ return 0;
+
+ FC_EXTRACT_VARARGS(fc_buffer, formatted_text);
+
+ const char* c;
+ Uint16 width = 0;
+ Uint16 bigWidth = 0; // Allows for multi-line strings
+
+ for (c = fc_buffer; *c != '\0'; c++)
+ {
+ if(*c == '\n')
+ {
+ bigWidth = bigWidth >= width? bigWidth : width;
+ width = 0;
+ continue;
+ }
+
+ FC_GlyphData glyph;
+ Uint32 codepoint = FC_GetCodepointFromUTF8(&c, 1);
+ if(FC_GetGlyphData(font, &glyph, codepoint) || FC_GetGlyphData(font, &glyph, ' '))
+ width += glyph.rect.w;
+ }
+ bigWidth = bigWidth >= width? bigWidth : width;
+
+ return bigWidth;
+}
+
+// If width == -1, use no width limit
+FC_Rect FC_GetCharacterOffset(FC_Font* font, Uint16 position_index, int column_width, const char* formatted_text, ...)
+{
+ FC_Rect result = {0, 0, 1, FC_GetLineHeight(font)};
+ FC_StringList *ls, *iter;
+ int num_lines = 0;
+ Uint8 done = 0;
+
+ if(formatted_text == NULL || column_width == 0 || position_index == 0 || font == NULL)
+ return result;
+
+ FC_EXTRACT_VARARGS(fc_buffer, formatted_text);
+
+ ls = FC_GetBufferFitToColumn(font, column_width, FC_MakeScale(1,1), 1);
+ for(iter = ls; iter != NULL;)
+ {
+ char* line;
+ int i = 0;
+ FC_StringList* next_iter = iter->next;
+
+ ++num_lines;
+ for(line = iter->value; line != NULL && *line != '\0'; line = (char*)U8_next(line))
+ {
+ ++i;
+ --position_index;
+ if(position_index == 0)
+ {
+ // FIXME: Doesn't handle box-wrapped newlines correctly
+ line = (char*)U8_next(line);
+ line[0] = '\0';
+ result.x = FC_GetWidth(font, "%s", iter->value);
+ done = 1;
+ break;
+ }
+ }
+ if(done)
+ break;
+
+ // Prevent line wrapping if there are no more lines
+ if(next_iter == NULL && !done)
+ result.x = FC_GetWidth(font, "%s", iter->value);
+ iter = next_iter;
+ }
+ FC_StringListFree(ls);
+
+ if(num_lines > 1)
+ {
+ result.y = (num_lines - 1) * FC_GetLineHeight(font);
+ }
+
+ return result;
+}
+
+
+Uint16 FC_GetColumnHeight(FC_Font* font, Uint16 width, const char* formatted_text, ...)
+{
+ int y = 0;
+
+ FC_StringList *ls, *iter;
+
+ if(font == NULL)
+ return 0;
+
+ if(formatted_text == NULL || width == 0)
+ return font->height;
+
+ FC_EXTRACT_VARARGS(fc_buffer, formatted_text);
+
+ ls = FC_GetBufferFitToColumn(font, width, FC_MakeScale(1,1), 0);
+ for(iter = ls; iter != NULL; iter = iter->next)
+ {
+ y += FC_GetLineHeight(font);
+ }
+ FC_StringListFree(ls);
+
+ return y;
+}
+
+static int FC_GetAscentFromCodepoint(FC_Font* font, Uint32 codepoint)
+{
+ FC_GlyphData glyph;
+
+ if(font == NULL)
+ return 0;
+
+ // FIXME: Store ascent so we can return it here
+ FC_GetGlyphData(font, &glyph, codepoint);
+ return glyph.rect.h;
+}
+
+static int FC_GetDescentFromCodepoint(FC_Font* font, Uint32 codepoint)
+{
+ FC_GlyphData glyph;
+
+ if(font == NULL)
+ return 0;
+
+ // FIXME: Store descent so we can return it here
+ FC_GetGlyphData(font, &glyph, codepoint);
+ return glyph.rect.h;
+}
+
+int FC_GetAscent(FC_Font* font, const char* formatted_text, ...)
+{
+ Uint32 codepoint;
+ int max, ascent;
+ const char* c;
+
+ if(font == NULL)
+ return 0;
+
+ if(formatted_text == NULL)
+ return font->ascent;
+
+ FC_EXTRACT_VARARGS(fc_buffer, formatted_text);
+
+ max = 0;
+ c = fc_buffer;
+
+ while(*c != '\0')
+ {
+ codepoint = FC_GetCodepointFromUTF8(&c, 1);
+ if(codepoint != 0)
+ {
+ ascent = FC_GetAscentFromCodepoint(font, codepoint);
+ if(ascent > max)
+ max = ascent;
+ }
+ ++c;
+ }
+ return max;
+}
+
+int FC_GetDescent(FC_Font* font, const char* formatted_text, ...)
+{
+ Uint32 codepoint;
+ int max, descent;
+ const char* c;
+
+ if(font == NULL)
+ return 0;
+
+ if(formatted_text == NULL)
+ return font->descent;
+
+ FC_EXTRACT_VARARGS(fc_buffer, formatted_text);
+
+ max = 0;
+ c = fc_buffer;
+
+ while(*c != '\0')
+ {
+ codepoint = FC_GetCodepointFromUTF8(&c, 1);
+ if(codepoint != 0)
+ {
+ descent = FC_GetDescentFromCodepoint(font, codepoint);
+ if(descent > max)
+ max = descent;
+ }
+ ++c;
+ }
+ return max;
+}
+
+int FC_GetBaseline(FC_Font* font)
+{
+ if(font == NULL)
+ return 0;
+
+ return font->baseline;
+}
+
+int FC_GetSpacing(FC_Font* font)
+{
+ if(font == NULL)
+ return 0;
+
+ return font->letterSpacing;
+}
+
+int FC_GetLineSpacing(FC_Font* font)
+{
+ if(font == NULL)
+ return 0;
+
+ return font->lineSpacing;
+}
+
+Uint16 FC_GetMaxWidth(FC_Font* font)
+{
+ if(font == NULL)
+ return 0;
+
+ return font->maxWidth;
+}
+
+SDL_Color FC_GetDefaultColor(FC_Font* font)
+{
+ if(font == NULL)
+ {
+ SDL_Color c = {0,0,0,255};
+ return c;
+ }
+
+ return font->default_color;
+}
+
+FC_Rect FC_GetBounds(FC_Font* font, float x, float y, FC_AlignEnum align, FC_Scale scale, const char* formatted_text, ...)
+{
+ FC_Rect result = {x, y, 0, 0};
+
+ if(formatted_text == NULL)
+ return result;
+
+ // Create a temp buffer while GetWidth and GetHeight use fc_buffer.
+ char* temp = (char*)malloc(fc_buffer_size);
+ FC_EXTRACT_VARARGS(temp, formatted_text);
+
+ result.w = FC_GetWidth(font, "%s", temp) * scale.x;
+ result.h = FC_GetHeight(font, "%s", temp) * scale.y;
+
+ switch(align)
+ {
+ case FC_ALIGN_LEFT:
+ break;
+ case FC_ALIGN_CENTER:
+ result.x -= result.w/2;
+ break;
+ case FC_ALIGN_RIGHT:
+ result.x -= result.w;
+ break;
+ default:
+ break;
+ }
+
+ free(temp);
+
+ return result;
+}
+
+Uint8 FC_InRect(float x, float y, FC_Rect input_rect)
+{
+ return (input_rect.x <= x && x <= input_rect.x + input_rect.w && input_rect.y <= y && y <= input_rect.y + input_rect.h);
+}
+
+// TODO: Make it work with alignment
+Uint16 FC_GetPositionFromOffset(FC_Font* font, float x, float y, int column_width, FC_AlignEnum align, const char* formatted_text, ...)
+{
+ FC_StringList *ls, *iter;
+ Uint8 done = 0;
+ int height = FC_GetLineHeight(font);
+ Uint16 position = 0;
+ int current_x = 0;
+ int current_y = 0;
+ FC_GlyphData glyph_data;
+
+ if(formatted_text == NULL || column_width == 0 || font == NULL)
+ return 0;
+
+ FC_EXTRACT_VARARGS(fc_buffer, formatted_text);
+
+ ls = FC_GetBufferFitToColumn(font, column_width, FC_MakeScale(1,1), 1);
+ for(iter = ls; iter != NULL; iter = iter->next)
+ {
+ char* line;
+
+ for(line = iter->value; line != NULL && *line != '\0'; line = (char*)U8_next(line))
+ {
+ if(FC_GetGlyphData(font, &glyph_data, FC_GetCodepointFromUTF8((const char**)&line, 0)))
+ {
+ if(FC_InRect(x, y, FC_MakeRect(current_x, current_y, glyph_data.rect.w, glyph_data.rect.h)))
+ {
+ done = 1;
+ break;
+ }
+
+ current_x += glyph_data.rect.w;
+ }
+ position++;
+ }
+ if(done)
+ break;
+
+ current_x = 0;
+ current_y += height;
+ if(y < current_y)
+ break;
+ }
+ FC_StringListFree(ls);
+
+ return position;
+}
+
+int FC_GetWrappedText(FC_Font* font, char* result, int max_result_size, Uint16 width, const char* formatted_text, ...)
+{
+ FC_StringList *ls, *iter;
+
+ if(font == NULL)
+ return 0;
+
+ if(formatted_text == NULL || width == 0)
+ return 0;
+
+ FC_EXTRACT_VARARGS(fc_buffer, formatted_text);
+
+ ls = FC_GetBufferFitToColumn(font, width, FC_MakeScale(1,1), 0);
+ int size_so_far = 0;
+ int size_remaining = max_result_size-1; // reserve for \0
+ for(iter = ls; iter != NULL && size_remaining > 0; iter = iter->next)
+ {
+ // Copy as much of this line as we can
+ int len = strlen(iter->value);
+ int num_bytes = FC_MIN(len, size_remaining);
+ memcpy(&result[size_so_far], iter->value, num_bytes);
+ size_so_far += num_bytes;
+
+ // If there's another line, add newline character
+ if(size_remaining > 0 && iter->next != NULL)
+ {
+ --size_remaining;
+ result[size_so_far] = '\n';
+ ++size_so_far;
+ }
+ }
+ FC_StringListFree(ls);
+
+ result[size_so_far] = '\0';
+
+ return size_so_far;
+}
+
+
+
+// Setters
+
+
+void FC_SetFilterMode(FC_Font* font, FC_FilterEnum filter)
+{
+ if(font == NULL)
+ return;
+
+ if(font->filter != filter)
+ {
+ font->filter = filter;
+
+ #ifdef FC_USE_SDL_GPU
+ // Update each texture to use this filter mode
+ {
+ int i;
+ GPU_FilterEnum gpu_filter = GPU_FILTER_NEAREST;
+ if(FC_GetFilterMode(font) == FC_FILTER_LINEAR)
+ gpu_filter = GPU_FILTER_LINEAR;
+
+ for(i = 0; i < font->glyph_cache_count; ++i)
+ {
+ GPU_SetImageFilter(font->glyph_cache[i], gpu_filter);
+ }
+ }
+ #endif
+ }
+}
+
+
+void FC_SetSpacing(FC_Font* font, int LetterSpacing)
+{
+ if(font == NULL)
+ return;
+
+ font->letterSpacing = LetterSpacing;
+}
+
+void FC_SetLineSpacing(FC_Font* font, int LineSpacing)
+{
+ if(font == NULL)
+ return;
+
+ font->lineSpacing = LineSpacing;
+}
+
+void FC_SetDefaultColor(FC_Font* font, SDL_Color color)
+{
+ if(font == NULL)
+ return;
+
+ font->default_color = color;
+}
diff --git a/src/SDL_FontCache.h b/src/SDL_FontCache.h
new file mode 100644
index 0000000..7ddd747
--- /dev/null
+++ b/src/SDL_FontCache.h
@@ -0,0 +1,321 @@
+/*
+SDL_FontCache v0.10.0: A font cache for SDL and SDL_ttf
+by Jonathan Dearborn
+Dedicated to the memory of Florian Hufsky
+
+License:
+ The short:
+ Use it however you'd like, but keep the copyright and license notice
+ whenever these files or parts of them are distributed in uncompiled form.
+
+ The long:
+Copyright (c) 2019 Jonathan Dearborn
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+*/
+
+#ifndef _SDL_FONTCACHE_H__
+#define _SDL_FONTCACHE_H__
+
+#include "SDL.h"
+#include "SDL_ttf.h"
+
+#ifdef FC_USE_SDL_GPU
+ #include "SDL_gpu.h"
+#endif
+
+
+#include <stdarg.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+// Let's pretend this exists...
+#define TTF_STYLE_OUTLINE 16
+
+
+
+// Differences between SDL_Renderer and SDL_gpu
+#ifdef FC_USE_SDL_GPU
+#define FC_Rect GPU_Rect
+#define FC_Target GPU_Target
+#define FC_Image GPU_Image
+#define FC_Log GPU_LogError
+#else
+#define FC_Rect SDL_Rect
+#define FC_Target SDL_Renderer
+#define FC_Image SDL_Texture
+#define FC_Log SDL_Log
+#endif
+
+
+// SDL_FontCache types
+
+typedef enum
+{
+ FC_ALIGN_LEFT,
+ FC_ALIGN_CENTER,
+ FC_ALIGN_RIGHT
+} FC_AlignEnum;
+
+typedef enum
+{
+ FC_FILTER_NEAREST,
+ FC_FILTER_LINEAR
+} FC_FilterEnum;
+
+typedef struct FC_Scale
+{
+ float x;
+ float y;
+
+} FC_Scale;
+
+typedef struct FC_Effect
+{
+ FC_AlignEnum alignment;
+ FC_Scale scale;
+ SDL_Color color;
+
+} FC_Effect;
+
+// Opaque type
+typedef struct FC_Font FC_Font;
+
+
+typedef struct FC_GlyphData
+{
+ SDL_Rect rect;
+ int cache_level;
+
+} FC_GlyphData;
+
+
+
+
+// Object creation
+
+FC_Rect FC_MakeRect(float x, float y, float w, float h);
+
+FC_Scale FC_MakeScale(float x, float y);
+
+SDL_Color FC_MakeColor(Uint8 r, Uint8 g, Uint8 b, Uint8 a);
+
+FC_Effect FC_MakeEffect(FC_AlignEnum alignment, FC_Scale scale, SDL_Color color);
+
+FC_GlyphData FC_MakeGlyphData(int cache_level, Sint16 x, Sint16 y, Uint16 w, Uint16 h);
+
+
+
+// Font object
+
+FC_Font* FC_CreateFont(void);
+
+#ifdef FC_USE_SDL_GPU
+Uint8 FC_LoadFont(FC_Font* font, const char* filename_ttf, Uint32 pointSize, SDL_Color color, int style);
+
+Uint8 FC_LoadFontFromTTF(FC_Font* font, TTF_Font* ttf, SDL_Color color);
+
+Uint8 FC_LoadFont_RW(FC_Font* font, SDL_RWops* file_rwops_ttf, Uint8 own_rwops, Uint32 pointSize, SDL_Color color, int style);
+#else
+Uint8 FC_LoadFont(FC_Font* font, SDL_Renderer* renderer, const char* filename_ttf, Uint32 pointSize, SDL_Color color, int style);
+
+Uint8 FC_LoadFontFromTTF(FC_Font* font, SDL_Renderer* renderer, TTF_Font* ttf, SDL_Color color);
+
+Uint8 FC_LoadFont_RW(FC_Font* font, SDL_Renderer* renderer, SDL_RWops* file_rwops_ttf, Uint8 own_rwops, Uint32 pointSize, SDL_Color color, int style);
+#endif
+
+#ifndef FC_USE_SDL_GPU
+// note: handle SDL event types SDL_RENDER_TARGETS_RESET(>= SDL 2.0.2) and SDL_RENDER_DEVICE_RESET(>= SDL 2.0.4)
+void FC_ResetFontFromRendererReset(FC_Font* font, SDL_Renderer* renderer, Uint32 evType);
+#endif
+
+void FC_ClearFont(FC_Font* font);
+
+void FC_FreeFont(FC_Font* font);
+
+
+
+// Built-in loading strings
+
+char* FC_GetStringASCII(void);
+
+char* FC_GetStringLatin1(void);
+
+char* FC_GetStringASCII_Latin1(void);
+
+
+// UTF-8 to SDL_FontCache codepoint conversion
+
+/*!
+Returns the Uint32 codepoint (not UTF-32) parsed from the given UTF-8 string.
+\param c A pointer to a string of proper UTF-8 character values.
+\param advance_pointer If true, the source pointer will be incremented to skip the extra bytes from multibyte codepoints.
+*/
+Uint32 FC_GetCodepointFromUTF8(const char** c, Uint8 advance_pointer);
+
+/*!
+Parses the given codepoint and stores the UTF-8 bytes in 'result'. The result is NULL terminated.
+\param result A memory buffer for the UTF-8 values. Must be at least 5 bytes long.
+\param codepoint The Uint32 codepoint to parse (not UTF-32).
+*/
+void FC_GetUTF8FromCodepoint(char* result, Uint32 codepoint);
+
+
+// UTF-8 string operations
+
+/*! Allocates a new string of 'size' bytes that is already NULL-terminated. The NULL byte counts toward the size limit, as usual. Returns NULL if size is 0. */
+char* U8_alloc(unsigned int size);
+
+/*! Deallocates the given string. */
+void U8_free(char* string);
+
+/*! Allocates a copy of the given string. */
+char* U8_strdup(const char* string);
+
+/*! Returns the number of UTF-8 characters in the given string. */
+int U8_strlen(const char* string);
+
+/*! Returns the number of bytes in the UTF-8 multibyte character pointed at by 'character'. */
+int U8_charsize(const char* character);
+
+/*! Copies the source multibyte character into the given buffer without overrunning it. Returns 0 on failure. */
+int U8_charcpy(char* buffer, const char* source, int buffer_size);
+
+/*! Returns a pointer to the next UTF-8 character. */
+const char* U8_next(const char* string);
+
+/*! Inserts a UTF-8 string into 'string' at the given position. Use a position of -1 to append. Returns 0 when unable to insert the string. */
+int U8_strinsert(char* string, int position, const char* source, int max_bytes);
+
+/*! Erases the UTF-8 character at the given position, moving the subsequent characters down. */
+void U8_strdel(char* string, int position);
+
+
+// Internal settings
+
+/*! Sets the string from which to load the initial glyphs. Use this if you need upfront loading for any reason (such as lack of render-target support). */
+void FC_SetLoadingString(FC_Font* font, const char* string);
+
+/*! Returns the size of the internal buffer which is used for unpacking variadic text data. This buffer is shared by all FC_Fonts. */
+unsigned int FC_GetBufferSize(void);
+
+/*! Changes the size of the internal buffer which is used for unpacking variadic text data. This buffer is shared by all FC_Fonts. */
+void FC_SetBufferSize(unsigned int size);
+
+void FC_SetRenderCallback(FC_Rect (*callback)(FC_Image* src, FC_Rect* srcrect, FC_Target* dest, float x, float y, float xscale, float yscale));
+
+FC_Rect FC_DefaultRenderCallback(FC_Image* src, FC_Rect* srcrect, FC_Target* dest, float x, float y, float xscale, float yscale);
+
+
+// Custom caching
+
+/*! Returns the number of cache levels that are active. */
+int FC_GetNumCacheLevels(FC_Font* font);
+
+/*! Returns the cache source texture at the given cache level. */
+FC_Image* FC_GetGlyphCacheLevel(FC_Font* font, int cache_level);
+
+// TODO: Specify ownership of the texture (should be shareable)
+/*! Sets a cache source texture for rendering. New cache levels must be sequential. */
+Uint8 FC_SetGlyphCacheLevel(FC_Font* font, int cache_level, FC_Image* cache_texture);
+
+/*! Copies the given surface to the given cache level as a texture. New cache levels must be sequential. */
+Uint8 FC_UploadGlyphCache(FC_Font* font, int cache_level, SDL_Surface* data_surface);
+
+
+/*! Returns the number of codepoints that are stored in the font's glyph data map. */
+unsigned int FC_GetNumCodepoints(FC_Font* font);
+
+/*! Copies the stored codepoints into the given array. */
+void FC_GetCodepoints(FC_Font* font, Uint32* result);
+
+/*! Stores the glyph data for the given codepoint in 'result'. Returns 0 if the codepoint was not found in the cache. */
+Uint8 FC_GetGlyphData(FC_Font* font, FC_GlyphData* result, Uint32 codepoint);
+
+/*! Sets the glyph data for the given codepoint. Duplicates are not checked. Returns a pointer to the stored data. */
+FC_GlyphData* FC_SetGlyphData(FC_Font* font, Uint32 codepoint, FC_GlyphData glyph_data);
+
+
+// Rendering
+
+FC_Rect FC_Draw(FC_Font* font, FC_Target* dest, float x, float y, const char* formatted_text, ...);
+FC_Rect FC_DrawAlign(FC_Font* font, FC_Target* dest, float x, float y, FC_AlignEnum align, const char* formatted_text, ...);
+FC_Rect FC_DrawScale(FC_Font* font, FC_Target* dest, float x, float y, FC_Scale scale, const char* formatted_text, ...);
+FC_Rect FC_DrawColor(FC_Font* font, FC_Target* dest, float x, float y, SDL_Color color, const char* formatted_text, ...);
+FC_Rect FC_DrawEffect(FC_Font* font, FC_Target* dest, float x, float y, FC_Effect effect, const char* formatted_text, ...);
+
+FC_Rect FC_DrawBox(FC_Font* font, FC_Target* dest, FC_Rect box, const char* formatted_text, ...);
+FC_Rect FC_DrawBoxAlign(FC_Font* font, FC_Target* dest, FC_Rect box, FC_AlignEnum align, const char* formatted_text, ...);
+FC_Rect FC_DrawBoxScale(FC_Font* font, FC_Target* dest, FC_Rect box, FC_Scale scale, const char* formatted_text, ...);
+FC_Rect FC_DrawBoxColor(FC_Font* font, FC_Target* dest, FC_Rect box, SDL_Color color, const char* formatted_text, ...);
+FC_Rect FC_DrawBoxEffect(FC_Font* font, FC_Target* dest, FC_Rect box, FC_Effect effect, const char* formatted_text, ...);
+
+FC_Rect FC_DrawColumn(FC_Font* font, FC_Target* dest, float x, float y, Uint16 width, const char* formatted_text, ...);
+FC_Rect FC_DrawColumnAlign(FC_Font* font, FC_Target* dest, float x, float y, Uint16 width, FC_AlignEnum align, const char* formatted_text, ...);
+FC_Rect FC_DrawColumnScale(FC_Font* font, FC_Target* dest, float x, float y, Uint16 width, FC_Scale scale, const char* formatted_text, ...);
+FC_Rect FC_DrawColumnColor(FC_Font* font, FC_Target* dest, float x, float y, Uint16 width, SDL_Color color, const char* formatted_text, ...);
+FC_Rect FC_DrawColumnEffect(FC_Font* font, FC_Target* dest, float x, float y, Uint16 width, FC_Effect effect, const char* formatted_text, ...);
+
+
+// Getters
+
+FC_FilterEnum FC_GetFilterMode(FC_Font* font);
+Uint16 FC_GetLineHeight(FC_Font* font);
+Uint16 FC_GetHeight(FC_Font* font, const char* formatted_text, ...);
+Uint16 FC_GetWidth(FC_Font* font, const char* formatted_text, ...);
+
+// Returns a 1-pixel wide box in front of the character in the given position (index)
+FC_Rect FC_GetCharacterOffset(FC_Font* font, Uint16 position_index, int column_width, const char* formatted_text, ...);
+Uint16 FC_GetColumnHeight(FC_Font* font, Uint16 width, const char* formatted_text, ...);
+
+int FC_GetAscent(FC_Font* font, const char* formatted_text, ...);
+int FC_GetDescent(FC_Font* font, const char* formatted_text, ...);
+int FC_GetBaseline(FC_Font* font);
+int FC_GetSpacing(FC_Font* font);
+int FC_GetLineSpacing(FC_Font* font);
+Uint16 FC_GetMaxWidth(FC_Font* font);
+SDL_Color FC_GetDefaultColor(FC_Font* font);
+
+FC_Rect FC_GetBounds(FC_Font* font, float x, float y, FC_AlignEnum align, FC_Scale scale, const char* formatted_text, ...);
+
+Uint8 FC_InRect(float x, float y, FC_Rect input_rect);
+// Given an offset (x,y) from the text draw position (the upper-left corner), returns the character position (UTF-8 index)
+Uint16 FC_GetPositionFromOffset(FC_Font* font, float x, float y, int column_width, FC_AlignEnum align, const char* formatted_text, ...);
+
+// Returns the number of characters in the new wrapped text written into `result`.
+int FC_GetWrappedText(FC_Font* font, char* result, int max_result_size, Uint16 width, const char* formatted_text, ...);
+
+// Setters
+
+void FC_SetFilterMode(FC_Font* font, FC_FilterEnum filter);
+void FC_SetSpacing(FC_Font* font, int LetterSpacing);
+void FC_SetLineSpacing(FC_Font* font, int LineSpacing);
+void FC_SetDefaultColor(FC_Font* font, SDL_Color color);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+
+
+#endif
diff --git a/src/System.cpp b/src/System.cpp
index 5f82815..e9d5672 100644
--- a/src/System.cpp
+++ b/src/System.cpp
@@ -1,188 +1,194 @@
//
// Created by Ido Mozes on 15/07/2019.
//
#include "System.h"
std::string getSurfaceName(EntityP &entity) {
std::string surfaceName;
- int damageStateLives = getLives<SELF_LIVES>(entity) / 5;
+ int damageStateLives = getBloonProperty<SELF_LIVES>(entity) / 5;
switch (entity->getComponent<Type>()->value) {
case BLOON_T: {
auto[regrowP, camoP, fortifiedP, glueP, gumP, corrosiveP, lives] = entity->getComponentsP<Regrow, Camo, Fortified, Glue, Gum, Corrosive, Lives>();
switch (entity->getComponent<Kind>()->value) {
case RED_BLOON:
surfaceName = "Red";
break;
case BLUE_BLOON:
surfaceName = "Blue";
break;
case GREEN_BLOON:
surfaceName = "Green";
break;
case YELLOW_BLOON:
surfaceName = "Yellow";
break;
case PINK_BLOON:
surfaceName = "Pink";
break;
case PURPLE_BLOON:
surfaceName = "Purple";
break;
case WHITE_BLOON:
surfaceName = "White";
break;
case BLACK_BLOON:
surfaceName = "Black";
break;
case ZEBRA_BLOON:
surfaceName = "Zebra";
break;
case LEAD_BLOON:
surfaceName = "Lead";
break;
case RAINBOW_BLOON:
surfaceName = "Rainbow";
break;
case CERAMIC_BLOON:
surfaceName = "Ceramic";
break;
case MOAB:
surfaceName = "Moab";
break;
case BFB:
surfaceName = "Bfb";
break;
case DDT:
surfaceName = "Ddt";
break;
case ZOMG:
surfaceName = "Zomg";
break;
case BAD:
surfaceName = "Bad";
break;
}
if (regrowP)
surfaceName += "Regrow";
if (camoP)
surfaceName += "Camo";
if (fortifiedP) {
surfaceName += "Fortified";
}
if (damageStateLives) {
- int maxLives = getLives<TOTAL_LIVES>(entity);
+ int maxLives = getBloonProperty<TOTAL_LIVES>(entity);
if (int damageState = (maxLives - lives->value) / damageStateLives)
surfaceName += "DamageState" + std::to_string(damageState);
}
if (gumP)
surfaceName += "Gum";
else if (glueP)
surfaceName += "Glue";
else if (corrosiveP)
surfaceName += "Slime";
break;
}
case TOWER_T: {
switch (entity->getComponent<Kind>()->value) {
case DART_MONKEY:
surfaceName = "DartMonkey";
break;
case SUPER_MONKEY:
surfaceName = "SuperMonkey";
break;
case SNIPER_MONKEY:
surfaceName = "SniperMonkey";
break;
}
break;
}
case SHOT_T: {
switch (entity->getComponent<Kind>()->value) {
case DART:
surfaceName = "Dart";
break;
+ case GUN:
+ surfaceName = "Dart";
+ break;
}
break;
}
}
return surfaceName;
}
float getSpeed(EntityP &entity) {
float speed;
switch (entity->getComponent<Type>()->value) {
case BLOON_T: {
auto[glueP, gumP, corrosiveP] = entity->getComponentsP<Glue, Gum, Corrosive>();
switch (entity->getComponent<Kind>()->value) {
case RED_BLOON:
speed = 3 * BPS;
break;
case BLUE_BLOON:
speed = 4 * BPS;
break;
case GREEN_BLOON:
speed = 5 * BPS;
break;
case YELLOW_BLOON:
speed = 10 * BPS;
break;
case PINK_BLOON:
speed = 11 * BPS;
break;
case PURPLE_BLOON:
speed = 9.5 * BPS;
break;
case WHITE_BLOON:
speed = 6 * BPS;
break;
case BLACK_BLOON:
speed = 5 * BPS;
break;
case ZEBRA_BLOON:
speed = 5 * BPS;
break;
case LEAD_BLOON:
speed = 3 * BPS;
break;
case RAINBOW_BLOON:
speed = 7 * BPS;
break;
case CERAMIC_BLOON:
speed = 8 * BPS;
break;
case MOAB:
speed = 3 * BPS;
break;
case BFB:
speed = 1 * BPS;
break;
case DDT:
speed = 9 * BPS;
break;
case ZOMG:
speed = 0.5 * BPS;
break;
case BAD:
speed = 0.2 * BPS;
break;
}
if (gumP or corrosiveP)
speed *= 0.8;
else if (glueP)
speed = 0;
break;
}
case SHOT_T: {
switch (entity->getComponent<Kind>()->value) {
case DART:
speed = 12;
break;
+ case GUN:
+ speed = 65;
+ break;
}
break;
}
}
return speed;
}
diff --git a/src/System.h b/src/System.h
index 8fb65af..9cddc6d 100644
--- a/src/System.h
+++ b/src/System.h
@@ -1,149 +1,147 @@
//
// Created by Ido Mozes on 23/06/2019.
//
#ifndef SDL2_GAME_SYSTEM_H
#define SDL2_GAME_SYSTEM_H
#include <initializer_list>
#include <vector>
#include <cmath>
#include "Entity.h"
#include "GameData.h"
#include "components/IntegerComponents.h"
#include "components/FloatComponents.h"
#include "components/FlagComponents.h"
#include "components/Sequence.h"
#include "components/Position.h"
#include "components/Visibility.h"
#include "components/PathIndex.h"
#include "components/Velocity.h"
#include "components/Draggable.h"
#include "components/Action.h"
#include "components/RangeShadow.h"
#include "components/AttackSpeed.h"
#include "components/DamageEvent.h"
#include "components/PoppedBloons.h"
#include "components/MoveEntityEvent.h"
typedef std::vector<EntityP> Entities;
inline void
operator+=(Entities &originalVector, Entities &newVector) {
originalVector.insert(originalVector.end(), std::make_move_iterator(newVector.begin()),
std::make_move_iterator(newVector.end()));
}
std::string getSurfaceName(EntityP &entity);
enum ReturnValue {
- TOTAL_LIVES, MIN_LIVES, SELF_LIVES
+ TOTAL_LIVES, MIN_LIVES, SELF_LIVES, YIELD
};
float getSpeed(EntityP &entity);
template<ReturnValue returnValue>
-int getLives(EntityP &entity) {
+int getBloonProperty(EntityP &entity) {
int totalLives = 0;
int selfLives = 0;
int minLives = 0;
+ int yield = 0;
int additionalPreviousFortifiedLives = 0;
- switch (entity->getComponent<Type>()->value) {
- case BLOON_T: {
- auto fortifiedP = entity->getComponent<Fortified>();
- switch (entity->getComponent<Kind>()->value) {
- case RED_BLOON:
- totalLives = 1;
- break;
- case BLUE_BLOON:
- totalLives = 2;
- break;
- case GREEN_BLOON:
- totalLives = 3;
- break;
- case YELLOW_BLOON:
- totalLives = 4;
- break;
- case PINK_BLOON:
- totalLives = 5;
- break;
- case PURPLE_BLOON:
- selfLives = 1;
- totalLives = 11;
- break;
- case WHITE_BLOON:
- selfLives = 1;
- totalLives = 11;
- break;
- case BLACK_BLOON:
- selfLives = 1;
- totalLives = 11;
- break;
- case ZEBRA_BLOON:
- selfLives = 1;
- totalLives = 23;
- break;
- case LEAD_BLOON:
- selfLives = 1;
- totalLives = 23;
- break;
- case RAINBOW_BLOON:
- selfLives = 1;
- totalLives = 47;
- break;
- case CERAMIC_BLOON:
- selfLives = 10;
- totalLives = 104;
- break;
- case MOAB:
- additionalPreviousFortifiedLives = 40;
- selfLives = 200;
- totalLives = 616;
- break;
- case BFB:
- additionalPreviousFortifiedLives = 960;
- selfLives = 1200;
- totalLives = 3164;
- break;
- case DDT:
- additionalPreviousFortifiedLives = 40;
- selfLives = 400;
- totalLives = 816;
- break;
- case ZOMG:
- additionalPreviousFortifiedLives = 8640;
- selfLives = 4000;
- totalLives = 16656;
- break;
- case BAD:
- additionalPreviousFortifiedLives = 26600;
- selfLives = 20000;
- totalLives = 55760;
+ auto fortifiedP = entity->getComponent<Fortified>();
+ switch (entity->getComponent<Kind>()->value) {
+ case RED_BLOON:
+ yield = totalLives = 1;
+ break;
+ case BLUE_BLOON:
+ yield = totalLives = 2;
+ break;
+ case GREEN_BLOON:
+ yield = totalLives = 3;
+ break;
+ case YELLOW_BLOON:
+ yield = totalLives = 4;
+ break;
+ case PINK_BLOON:
+ yield = totalLives = 5;
+ break;
+ case PURPLE_BLOON:
+ yield = totalLives = 11;
+ break;
+ case WHITE_BLOON:
+ yield = totalLives = 11;
+ break;
+ case BLACK_BLOON:
+ yield = totalLives = 11;
+ break;
+ case ZEBRA_BLOON:
+ yield = totalLives = 23;
+ break;
+ case LEAD_BLOON:
+ yield = totalLives = 23;
+ break;
+ case RAINBOW_BLOON:
+ yield = totalLives = 47;
+ break;
+ case CERAMIC_BLOON:
+ selfLives = 10;
+ yield = 95;
+ totalLives = 104;
+ break;
+ case MOAB:
+ additionalPreviousFortifiedLives = 40;
+ yield = 381;
+ selfLives = 200;
+ totalLives = 616;
+ break;
+ case BFB:
+ additionalPreviousFortifiedLives = 960;
+ yield = 1525;
+ selfLives = 1200;
+ totalLives = 3164;
+ break;
+ case DDT:
+ additionalPreviousFortifiedLives = 40;
+ yield = 381;
+ selfLives = 400;
+ totalLives = 816;
+ break;
+ case ZOMG:
+ additionalPreviousFortifiedLives = 8640;
+ yield = 6101;
+ selfLives = 4000;
+ totalLives = 16656;
+ break;
+ case BAD:
+ additionalPreviousFortifiedLives = 26600;
+ yield = 13346;
+ selfLives = 20000;
+ totalLives = 55760;
- break;
- }
- minLives = totalLives - selfLives + 1;
- if (fortifiedP) {
- totalLives += additionalPreviousFortifiedLives + selfLives;
- selfLives *= 2;
- minLives += additionalPreviousFortifiedLives;
- }
- break;
- }
+ break;
+ }
+ minLives = totalLives - selfLives + 1;
+ if (fortifiedP) {
+ totalLives += additionalPreviousFortifiedLives + selfLives;
+ selfLives *= 2;
+ minLives += additionalPreviousFortifiedLives;
}
switch (returnValue) {
case TOTAL_LIVES:
return totalLives;
case MIN_LIVES:
return minLives;
case SELF_LIVES:
return selfLives;
+ case YIELD:
+ return yield;
}
}
class System {
public:
virtual void update(Entities *entities, GameData &gameData) = 0;
};
#endif //SDL2_GAME_SYSTEM_H
diff --git a/src/components/DamageEvent.h b/src/components/DamageEvent.h
index 0bacd9f..1edb9e2 100644
--- a/src/components/DamageEvent.h
+++ b/src/components/DamageEvent.h
@@ -1,19 +1,19 @@
//
// Created by Ido Mozes on 16/07/2019.
//
#ifndef SDL_GAME_DAMAGEEVENT_H
#define SDL_GAME_DAMAGEEVENT_H
#include "../Component.h"
class DamageEvent : public Component {
public:
int damage;
EntityP shot;
static constexpr ComponentType type = ComponentType::DAMAGE_EVENT;
- DamageEvent(int damage, EntityP &shot) : damage(damage), shot(shot) {}
+ DamageEvent(int damage, EntityP shot) : damage(damage), shot(shot) {}
};
#endif //SDL_GAME_DAMAGEEVENT_H
diff --git a/src/components/IntegerComponents.h b/src/components/IntegerComponents.h
index 692bc78..bffa3b4 100644
--- a/src/components/IntegerComponents.h
+++ b/src/components/IntegerComponents.h
@@ -1,66 +1,66 @@
//
// Created by Ido Mozes on 14/07/2019.
//
#ifndef SDL_GAME_INTEGERCOMPONENTS_H
#define SDL_GAME_INTEGERCOMPONENTS_H
#define INTEGER_COMPONENT(className, classType) class className : public IntegerComponent { public: static constexpr ComponentType type = ComponentType::classType; using IntegerComponent::IntegerComponent; }
#include "../Component.h"
class IntegerComponent : public Component {
public:
int value;
explicit IntegerComponent(int value) : value(value) {}
};
enum Types {
OBSTACLE_T, TOWER_T, BLOON_T, SHOT_T, SEQUENCE_T
};
enum Strategies {
FIRST, LAST, CLOSEST, STRONGEST
};
enum BloonKinds{
RED_BLOON,
BLUE_BLOON,
GREEN_BLOON,
YELLOW_BLOON,
PINK_BLOON,
PURPLE_BLOON,
+ ZEBRA_BLOON,
WHITE_BLOON,
BLACK_BLOON,
- ZEBRA_BLOON,
LEAD_BLOON,
RAINBOW_BLOON,
CERAMIC_BLOON,
MOAB,
BFB,
- DDT,
ZOMG,
+ DDT,
BAD
};
enum TowerKinds{
DART_MONKEY,
SUPER_MONKEY,
SNIPER_MONKEY,
};
enum ShotKinds{
DART,
BOMB,
SPRAY,
GUN,
};
INTEGER_COMPONENT(Lives,LIVES);
INTEGER_COMPONENT(Damage,DAMAGE);
INTEGER_COMPONENT(Pierce,PIERCE);
INTEGER_COMPONENT(Type,TYPE);
INTEGER_COMPONENT(Strategy,STRATEGY);
INTEGER_COMPONENT(Cost,COST);
INTEGER_COMPONENT(Value,VALUE);
INTEGER_COMPONENT(Kind,KIND);
INTEGER_COMPONENT(ShotKind,SHOT_KIND);
#endif //SDL_GAME_INTEGERCOMPONENTS_H
diff --git a/src/main.cpp b/src/main.cpp
index b45fc7a..e754cb5 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1,21 +1,21 @@
//#include <iostream>
#include "SDL.h"
#include "Game.h"
int main(int argc, char *argv[]) {
- const int FPS = 200, frameDelay = 1000 / FPS;
+ const int FPS = 60, frameDelay = 1000 / FPS;
Uint32 frameStart;
int frameTime;
Game game(false, 1.5);
while (game.running()) {
frameStart = SDL_GetTicks();
game.update();
frameTime = SDL_GetTicks() - frameStart;
if (frameDelay > frameTime) {
SDL_Delay(frameDelay - frameTime);
}
}
return 0;
}
\ No newline at end of file
diff --git a/src/systems/BloonsSpawnSystem.cpp b/src/systems/BloonsSpawnSystem.cpp
index eff590d..8d55041 100644
--- a/src/systems/BloonsSpawnSystem.cpp
+++ b/src/systems/BloonsSpawnSystem.cpp
@@ -1,35 +1,35 @@
//
// Created by Ido Mozes on 07/07/2019.
//
#include "BloonsSpawnSystem.h"
void BloonsSpawnSystem::update(Entities *layers, GameData &gameData) {
for (auto &entity: layers[SEQUENCES_LAYER]) {
auto[sequence, kind] = entity->getComponents<Sequence, Kind>().value();
auto [regrowP,camoP,fortifiedP] = entity->getComponentsP<Regrow,Camo,Fortified>();
int amount = sequence.getAmountReady();
for (int j = 0; j < amount; ++j) {
EntityP bloon(new Entity());
bloon->addComponent<Type>(BLOON_T);
bloon->addComponent<Position>(gameData.startingPoint);
bloon->addComponent<PathIndex>(0);
bloon->addComponents(kind);
if (regrowP)
bloon->addComponent<Regrow>();
if (camoP)
bloon->addComponent<Camo>();
if (fortifiedP)
bloon->addComponent<Fortified>();
- bloon->addComponent<Lives>(getLives<TOTAL_LIVES>(bloon));
+ bloon->addComponent<Lives>(getBloonProperty<TOTAL_LIVES>(bloon));
SDL_Surface *surface = gameData.assets[getSurfaceName(bloon)];
bloon->addComponent<Visibility>(gameData.renderer, surface,
SDL_Rect{0, 0, int(surface->w / 3), int(surface->h / 3)});
- bloon->addComponent<Range>(std::min(surface->w / 6, surface->h / 6));
+ bloon->addComponent<Range>(std::max(surface->w / 6, surface->h / 6));
layers[BLOONS_LAYER].emplace_back(bloon);
}
}
}
diff --git a/src/systems/DamageSystem.cpp b/src/systems/DamageSystem.cpp
index edd7b9a..3f327be 100644
--- a/src/systems/DamageSystem.cpp
+++ b/src/systems/DamageSystem.cpp
@@ -1,419 +1,208 @@
//
// Created by Ido Mozes on 15/07/2019.
//
#include "DamageSystem.h"
bool didBloonPop(EntityP &bloon, int &lives, int &damage) {
if (bloon->getComponent<Fortified>()) {
if (damage == 1) {
lives -= damage;
if (lives % 2 == 1)
return false;
} else {
lives -= 2;
damage -= 2;
}
} else {
lives -= 1;
damage -= 1;
}
return true;
}
bool didBloonPop(EntityP &bloon, int &lives, int &damage, int minLives) {
if (lives - damage >= minLives) {
lives -= damage;
return false;
}
damage -= lives - minLives + 1;
lives = minLives - 1;
return true;
}
+EntityP
+spawnBloon(Entities &newBloons, GameData &gameData, BloonKinds kind, EntityP &shot, int lives, float progress,
+ bool regrow, bool camo,
+ bool fortified, bool gum,
+ bool glue, bool corrosive) {
+ EntityP newBloon(new Entity());
+ if (shot)
+ shot->getComponent<PoppedBloons>()->value.emplace(newBloon.get());
+ newBloon->addComponent<Type>(BLOON_T);
+ newBloon->addComponent<Kind>(kind);
+ newBloon->addComponent<Lives>(lives);
+ newBloon->addComponent<Position>(gameData.startingPoint);
+ newBloon->addComponent<PathIndex>(0);
+ newBloon->getComponent<PathIndex>()->progress = progress;
+ if (regrow)
+ newBloon->addComponent<Regrow>();
+ if (camo)
+ newBloon->addComponent<Camo>();
+ if (fortified)
+ newBloon->addComponent<Fortified>();
+ if (gum)
+ newBloon->addComponent<Gum>();
+ else if (glue)
+ newBloon->addComponent<Glue>();
+ else if (corrosive)
+ newBloon->addComponent<Corrosive>();
+ SDL_Surface *surface = gameData.assets[getSurfaceName(newBloon)];
+ newBloon->addComponent<Range>(std::max(surface->w / 6, surface->h / 6));
+ newBloon->addComponent<Visibility>(gameData.renderer, surface, SDL_Rect{0, 0, surface->w / 3, 0});
+ newBloons.emplace_back(newBloon);
+ return newBloon;
+}
+
void damageBloon(EntityP &bloon, EntityP &shot, int damage, GameData &gameData, Entities &newBloons) {
if (damage == 0)
return;
auto &lives = bloon->getComponent<Lives>()->value;
if (damage >= lives) {
+ gameData.money += getBloonProperty<YIELD>(bloon);
bloon->addComponent<RemoveEntityEvent>();
return;
}
auto &kind = bloon->getComponent<Kind>()->value;
+ float progress = bloon->getComponent<PathIndex>()->progress;
+ SDL_Surface *surface;
auto &visibility = *bloon->getComponent<Visibility>();
+ if (kind > PINK_BLOON) {
+ if (kind >= CERAMIC_BLOON and !didBloonPop(bloon, lives, damage, getBloonProperty<MIN_LIVES>(bloon))) {
+ surface = gameData.assets[getSurfaceName(bloon)];
+ visibility.loadTexture(gameData.renderer, surface);
+ return;
+ }
+ if (kind < CERAMIC_BLOON and !didBloonPop(bloon, lives, damage))
+ return;
+ }
+ if (kind > PINK_BLOON) {
+ gameData.money += 1;
+ bloon->addComponent<RemoveEntityEvent>();
+ }
auto[regrowP, camoP, fortifiedP, glueP, gumP, corrosiveP] = bloon->getComponentsP<Regrow, Camo, Fortified, Glue, Gum, Corrosive>();
- SDL_Surface *surface;
switch (kind) {
case RED_BLOON:
lives -= damage;
break;
case BLUE_BLOON:
case GREEN_BLOON:
case YELLOW_BLOON:
case PINK_BLOON: {
lives -= damage;
if (fortifiedP and damage == 1 and lives % 2 == 1)
break;
-
+ gameData.money += kind - ((fortifiedP ? lives / 2 : lives) - 1);
kind = (fortifiedP ? lives / 2 : lives) - 1;
surface = gameData.assets[getSurfaceName(bloon)];
visibility.setDstRect(SDL_Rect{0, 0, surface->w / 3, 0});
visibility.loadTexture(gameData.renderer, surface);
bloon->getComponent<Range>()->value = std::max(surface->w / 6, surface->h / 6);
break;
}
case PURPLE_BLOON:
case WHITE_BLOON:
case BLACK_BLOON: {
- if (!didBloonPop(bloon, lives, damage))
- break;
- auto &pathIndex = *bloon->getComponent<PathIndex>();
for (int i = 0; i < 2; ++i) {
- EntityP newBloon(new Entity());
- shot->getComponent<PoppedBloons>()->value.emplace(newBloon.get());
- newBloon->addComponent<Type>(BLOON_T);
- newBloon->addComponent<Kind>(PINK_BLOON);
- newBloon->addComponent<Lives>(lives / 2);
- newBloon->addComponent<Position>(gameData.startingPoint);
- newBloon->addComponent<PathIndex>(0);
- newBloon->getComponent<PathIndex>()->progress =
- i == 0 ? std::fmaxf(0, pathIndex.progress - 10) : std::fminf(gameData.path.size() - 1,
- pathIndex.progress + 10);
- if (regrowP)
- newBloon->addComponent<Regrow>();
- if (camoP)
- newBloon->addComponent<Camo>();
- if (gumP)
- newBloon->addComponent<Gum>();
- else if (glueP)
- newBloon->addComponent<Glue>();
- else if (corrosiveP)
- newBloon->addComponent<Corrosive>();
- surface = gameData.assets[getSurfaceName(newBloon)];
- newBloon->addComponent<Range>(std::max(surface->w / 6, surface->h / 6));
- newBloon->addComponent<Visibility>(gameData.renderer, surface, SDL_Rect{0, 0, surface->w / 3, 0});
- newBloons.emplace_back(newBloon);
+ EntityP newBloon = spawnBloon(newBloons, gameData, PINK_BLOON, shot, lives / 2,
+ i == 0 ? std::fmaxf(0, progress - 10) : std::fminf(
+ gameData.path.size() - 1, progress + 10), regrowP, camoP, false,
+ gumP, glueP, corrosiveP);
damageBloon(newBloon, shot, i == 0 ? ceilf(damage / 2.0) : floorf(damage / 2.0), gameData, newBloons);
}
- bloon->addComponent<RemoveEntityEvent>();
break;
}
case ZEBRA_BLOON: {
- if (!didBloonPop(bloon, lives, damage))
- break;
- auto &pathIndex = *bloon->getComponent<PathIndex>();
for (int i = 0; i < 2; ++i) {
- EntityP newBloon(new Entity());
- shot->getComponent<PoppedBloons>()->value.emplace(newBloon.get());
- newBloon->addComponent<Type>(BLOON_T);
- newBloon->addComponent<Kind>(i == 0 ? BLACK_BLOON : WHITE_BLOON);
- newBloon->addComponent<Lives>(lives / 2);
- newBloon->addComponent<Position>(gameData.startingPoint);
- newBloon->addComponent<PathIndex>(0);
- newBloon->getComponent<PathIndex>()->progress =
- i == 0 ? std::fmaxf(0, pathIndex.progress - 10) : std::fminf(gameData.path.size() - 1,
- pathIndex.progress + 10);
- if (regrowP)
- newBloon->addComponent<Regrow>();
- if (camoP)
- newBloon->addComponent<Camo>();
- if (gumP)
- newBloon->addComponent<Gum>();
- else if (glueP)
- newBloon->addComponent<Glue>();
- else if (corrosiveP)
- newBloon->addComponent<Corrosive>();
- surface = gameData.assets[getSurfaceName(newBloon)];
- newBloon->addComponent<Range>(std::max(surface->w / 6, surface->h / 6));
- newBloon->addComponent<Visibility>(gameData.renderer, surface, SDL_Rect{0, 0, surface->w / 3, 0});
- newBloons.emplace_back(newBloon);
+ EntityP newBloon = spawnBloon(newBloons, gameData, i == 0 ? BLACK_BLOON : WHITE_BLOON, shot, lives / 2,
+ i == 0 ? std::fmaxf(0, progress - 10) : std::fminf(
+ gameData.path.size() - 1, progress + 10), regrowP, camoP, false,
+ gumP, glueP, corrosiveP);
damageBloon(newBloon, shot, i == 0 ? ceilf(damage / 2.0) : floorf(damage / 2.0), gameData, newBloons);
}
- bloon->addComponent<RemoveEntityEvent>();
break;
}
- case LEAD_BLOON: {
- if (!didBloonPop(bloon, lives, damage))
- break;
- auto &pathIndex = *bloon->getComponent<PathIndex>();
+ case LEAD_BLOON:
+ case RAINBOW_BLOON:
+ case CERAMIC_BLOON: {
for (int i = 0; i < 2; ++i) {
- EntityP newBloon(new Entity());
- shot->getComponent<PoppedBloons>()->value.emplace(newBloon.get());
- newBloon->addComponent<Type>(BLOON_T);
- newBloon->addComponent<Kind>(BLACK_BLOON);
- newBloon->addComponent<Lives>(lives / 2);
- newBloon->addComponent<Position>(gameData.startingPoint);
- newBloon->addComponent<PathIndex>(0);
- newBloon->getComponent<PathIndex>()->progress =
- i == 0 ? std::fmaxf(0, pathIndex.progress - 10) : std::fminf(gameData.path.size() - 1,
- pathIndex.progress + 10);
- if (regrowP)
- newBloon->addComponent<Regrow>();
- if (camoP)
- newBloon->addComponent<Camo>();
- if (gumP)
- newBloon->addComponent<Gum>();
- else if (glueP)
- newBloon->addComponent<Glue>();
- else if (corrosiveP)
- newBloon->addComponent<Corrosive>();
- surface = gameData.assets[getSurfaceName(newBloon)];
- newBloon->addComponent<Range>(std::max(surface->w / 6, surface->h / 6));
- newBloon->addComponent<Visibility>(gameData.renderer, surface, SDL_Rect{0, 0, surface->w / 3, 0});
- newBloons.emplace_back(newBloon);
- damageBloon(newBloon, shot, i == 0 ? ceilf(damage / 2.0) : floorf(damage / 2.0), gameData, newBloons);
+ EntityP newBloon = spawnBloon(newBloons, gameData, BloonKinds(kind - 1), shot, lives / 2,
+ i == 0 ? std::fmaxf(0, progress - 10) : std::fminf(
+ gameData.path.size() - 1, progress + 10), regrowP, camoP, false,
+ gumP, glueP, corrosiveP);
+ damageBloon(newBloon, shot, i == 0 ? ceilf(damage / 2.0) : floorf(damage / 2.0), gameData,
+ newBloons);
}
- bloon->addComponent<RemoveEntityEvent>();
break;
}
- case RAINBOW_BLOON: {
- if (!didBloonPop(bloon, lives, damage))
- break;
- auto &pathIndex = *bloon->getComponent<PathIndex>();
- for (int i = 0; i < 2; ++i) {
+ case MOAB:
+ case BFB:
+ case ZOMG:
+ case DDT: {
+ for (int i = 0; i < 4; ++i) {
+ EntityP newBloon = spawnBloon(newBloons, gameData, kind == DDT ? CERAMIC_BLOON : BloonKinds(kind - 1),
+ shot, lives / 4,
+ (i < 2 ? std::fmaxf(0, progress - 20 + 10 * (i % 2)) : std::fminf(
+ gameData.path.size() - 1, progress + 20 - 10 * (i % 2))),
+ kind == DDT, kind == DDT, fortifiedP, false, false, false);
+ damageBloon(newBloon, shot, i == 0 ? ceilf(damage / 4.0) : damage - ceilf(damage / 4.0) * 3,
+ gameData, newBloons);
+ }
+ break;
+ }
+ case BAD: {
+ for (int i = 0; i < 5; ++i) {
EntityP newBloon(new Entity());
- shot->getComponent<PoppedBloons>()->value.emplace(newBloon.get());
+ if (shot)
+ shot->getComponent<PoppedBloons>()->value.emplace(newBloon.get());
newBloon->addComponent<Type>(BLOON_T);
- newBloon->addComponent<Kind>(ZEBRA_BLOON);
- newBloon->addComponent<Lives>(lives / 2);
+ newBloon->addComponent<Kind>(i < 2 ? ZOMG : DDT);
+ if (fortifiedP)
+ newBloon->addComponent<Fortified>();
+ if (i >= 2)
+ newBloon->addComponent<Camo>();
+ newBloon->addComponent<Lives>(getBloonProperty<TOTAL_LIVES>(newBloon));
newBloon->addComponent<Position>(gameData.startingPoint);
newBloon->addComponent<PathIndex>(0);
newBloon->getComponent<PathIndex>()->progress =
- i == 0 ? std::fmaxf(0, pathIndex.progress - 10) : std::fminf(gameData.path.size() - 1,
- pathIndex.progress + 10);
- if (regrowP)
- newBloon->addComponent<Regrow>();
- if (camoP)
- newBloon->addComponent<Camo>();
- if (gumP)
- newBloon->addComponent<Gum>();
- else if (glueP)
- newBloon->addComponent<Glue>();
- else if (corrosiveP)
- newBloon->addComponent<Corrosive>();
+ i < 2 ? std::fmaxf(0, progress - 20 + 10 * (i % 2)) : std::fminf(
+ gameData.path.size() - 1, progress + 30 - 10 * (i % 3));
surface = gameData.assets[getSurfaceName(newBloon)];
newBloon->addComponent<Range>(std::max(surface->w / 6, surface->h / 6));
newBloon->addComponent<Visibility>(gameData.renderer, surface, SDL_Rect{0, 0, surface->w / 3, 0});
newBloons.emplace_back(newBloon);
- damageBloon(newBloon, shot, i == 0 ? ceilf(damage / 2.0) : floorf(damage / 2.0), gameData, newBloons);
- }
- bloon->addComponent<RemoveEntityEvent>();
- break;
- }
- case CERAMIC_BLOON: {
- if (didBloonPop(bloon, lives, damage, getLives<MIN_LIVES>(bloon))) {
- auto &pathIndex = *bloon->getComponent<PathIndex>();
- for (int i = 0; i < 2; ++i) {
- EntityP newBloon(new Entity());
- shot->getComponent<PoppedBloons>()->value.emplace(newBloon.get());
- newBloon->addComponent<Type>(BLOON_T);
- newBloon->addComponent<Kind>(RAINBOW_BLOON);
- newBloon->addComponent<Lives>(lives / 2);
- newBloon->addComponent<Position>(gameData.startingPoint);
- newBloon->addComponent<PathIndex>(0);
- newBloon->getComponent<PathIndex>()->progress =
- i == 0 ? std::fmaxf(0, pathIndex.progress - 10) : std::fminf(gameData.path.size() - 1,
- pathIndex.progress + 10);
- if (regrowP)
- newBloon->addComponent<Regrow>();
- if (camoP)
- newBloon->addComponent<Camo>();
- if (gumP)
- newBloon->addComponent<Gum>();
- else if (glueP)
- newBloon->addComponent<Glue>();
- else if (corrosiveP)
- newBloon->addComponent<Corrosive>();
- surface = gameData.assets[getSurfaceName(newBloon)];
- newBloon->addComponent<Range>(std::max(surface->w / 6, surface->h / 6));
- newBloon->addComponent<Visibility>(gameData.renderer, surface, SDL_Rect{0, 0, surface->w / 3, 0});
- newBloons.emplace_back(newBloon);
- damageBloon(newBloon, shot, i == 0 ? ceilf(damage / 2.0) : floorf(damage / 2.0), gameData,
- newBloons);
- }
- bloon->addComponent<RemoveEntityEvent>();
- break;
- } else {
- surface = gameData.assets[getSurfaceName(bloon)];
- visibility.loadTexture(gameData.renderer, surface);
- }
- break;
- }
- case MOAB: {
- if (didBloonPop(bloon, lives, damage, getLives<MIN_LIVES>(bloon))) {
- auto &pathIndex = *bloon->getComponent<PathIndex>();
- for (int i = 0; i < 4; ++i) {
- EntityP newBloon(new Entity());
- shot->getComponent<PoppedBloons>()->value.emplace(newBloon.get());
- newBloon->addComponent<Type>(BLOON_T);
- newBloon->addComponent<Kind>(CERAMIC_BLOON);
- newBloon->addComponent<Lives>(lives / 4);
- newBloon->addComponent<Position>(gameData.startingPoint);
- newBloon->addComponent<PathIndex>(0);
- newBloon->getComponent<PathIndex>()->progress =
- i < 2 ? std::fmaxf(0, pathIndex.progress - 20 + 10 * (i % 2)) : std::fminf(
- gameData.path.size() - 1, pathIndex.progress + 20 - 10 * (i % 2));
- if (fortifiedP)
- newBloon->addComponent<Fortified>();
- surface = gameData.assets[getSurfaceName(newBloon)];
- newBloon->addComponent<Range>(std::max(surface->w / 6, surface->h / 6));
- newBloon->addComponent<Visibility>(gameData.renderer, surface, SDL_Rect{0, 0, surface->w / 3, 0});
- newBloons.emplace_back(newBloon);
- damageBloon(newBloon, shot, i == 0 ? ceilf(damage / 4.0) : damage - ceilf(damage / 4.0) * 3,
- gameData, newBloons);
- }
- bloon->addComponent<RemoveEntityEvent>();
- break;
- } else {
- surface = gameData.assets[getSurfaceName(bloon)];
- visibility.loadTexture(gameData.renderer, surface);
- }
- break;
- }
- case BFB:{
- if (didBloonPop(bloon, lives, damage, getLives<MIN_LIVES>(bloon))) {
- auto &pathIndex = *bloon->getComponent<PathIndex>();
- for (int i = 0; i < 4; ++i) {
- EntityP newBloon(new Entity());
- shot->getComponent<PoppedBloons>()->value.emplace(newBloon.get());
- newBloon->addComponent<Type>(BLOON_T);
- newBloon->addComponent<Kind>(MOAB);
- newBloon->addComponent<Lives>(lives / 4);
- newBloon->addComponent<Position>(gameData.startingPoint);
- newBloon->addComponent<PathIndex>(0);
- newBloon->getComponent<PathIndex>()->progress =
- i < 2 ? std::fmaxf(0, pathIndex.progress - 20 + 10 * (i % 2)) : std::fminf(
- gameData.path.size() - 1, pathIndex.progress + 20 - 10 * (i % 2));
- if (fortifiedP)
- newBloon->addComponent<Fortified>();
- surface = gameData.assets[getSurfaceName(newBloon)];
- newBloon->addComponent<Range>(std::max(surface->w / 6, surface->h / 6));
- newBloon->addComponent<Visibility>(gameData.renderer, surface, SDL_Rect{0, 0, surface->w / 3, 0});
- newBloons.emplace_back(newBloon);
- damageBloon(newBloon, shot, i == 0 ? ceilf(damage / 4.0) : damage - ceilf(damage / 4.0) * 3,
- gameData, newBloons);
- }
- bloon->addComponent<RemoveEntityEvent>();
- break;
- } else {
- surface = gameData.assets[getSurfaceName(bloon)];
- visibility.loadTexture(gameData.renderer, surface);
- }
- break;
- }
- case DDT:{
- if (didBloonPop(bloon, lives, damage, getLives<MIN_LIVES>(bloon))) {
- auto &pathIndex = *bloon->getComponent<PathIndex>();
- for (int i = 0; i < 4; ++i) {
- EntityP newBloon(new Entity());
- shot->getComponent<PoppedBloons>()->value.emplace(newBloon.get());
- newBloon->addComponent<Type>(BLOON_T);
- newBloon->addComponent<Kind>(CERAMIC_BLOON);
- newBloon->addComponent<Lives>(lives / 4);
- newBloon->addComponent<Position>(gameData.startingPoint);
- newBloon->addComponent<PathIndex>(0);
- newBloon->getComponent<PathIndex>()->progress =
- i < 2 ? std::fmaxf(0, pathIndex.progress - 20 + 10 * (i % 2)) : std::fminf(
- gameData.path.size() - 1, pathIndex.progress + 20 - 10 * (i % 2));
- if (fortifiedP)
- newBloon->addComponent<Fortified>();
- newBloon->addComponent<Regrow>();
- newBloon->addComponent<Camo>();
- surface = gameData.assets[getSurfaceName(newBloon)];
- newBloon->addComponent<Range>(std::max(surface->w / 6, surface->h / 6));
- newBloon->addComponent<Visibility>(gameData.renderer, surface, SDL_Rect{0, 0, surface->w / 3, 0});
- newBloons.emplace_back(newBloon);
- damageBloon(newBloon, shot, i == 0 ? ceilf(damage / 4.0) : damage - ceilf(damage / 4.0) * 3,
- gameData, newBloons);
- }
- bloon->addComponent<RemoveEntityEvent>();
- break;
- } else {
- surface = gameData.assets[getSurfaceName(bloon)];
- visibility.loadTexture(gameData.renderer, surface);
- }
- break;
- }
- case ZOMG:{
- if (didBloonPop(bloon, lives, damage, getLives<MIN_LIVES>(bloon))) {
- auto &pathIndex = *bloon->getComponent<PathIndex>();
- for (int i = 0; i < 4; ++i) {
- EntityP newBloon(new Entity());
- shot->getComponent<PoppedBloons>()->value.emplace(newBloon.get());
- newBloon->addComponent<Type>(BLOON_T);
- newBloon->addComponent<Kind>(BFB);
- newBloon->addComponent<Lives>(lives / 4);
- newBloon->addComponent<Position>(gameData.startingPoint);
- newBloon->addComponent<PathIndex>(0);
- newBloon->getComponent<PathIndex>()->progress =
- i < 2 ? std::fmaxf(0, pathIndex.progress - 20 + 10 * (i % 2)) : std::fminf(
- gameData.path.size() - 1, pathIndex.progress + 20 - 10 * (i % 2));
- if (fortifiedP)
- newBloon->addComponent<Fortified>();
- surface = gameData.assets[getSurfaceName(newBloon)];
- newBloon->addComponent<Range>(std::max(surface->w / 6, surface->h / 6));
- newBloon->addComponent<Visibility>(gameData.renderer, surface, SDL_Rect{0, 0, surface->w / 3, 0});
- newBloons.emplace_back(newBloon);
- damageBloon(newBloon, shot, i == 0 ? ceilf(damage / 4.0) : damage - ceilf(damage / 4.0) * 3,
- gameData, newBloons);
- }
- bloon->addComponent<RemoveEntityEvent>();
- break;
- } else {
- surface = gameData.assets[getSurfaceName(bloon)];
- visibility.loadTexture(gameData.renderer, surface);
- }
- break;
- }
- case BAD:{
- if (didBloonPop(bloon, lives, damage, getLives<MIN_LIVES>(bloon))) {
- auto &pathIndex = *bloon->getComponent<PathIndex>();
- for (int i = 0; i < 5; ++i) {
- EntityP newBloon(new Entity());
- shot->getComponent<PoppedBloons>()->value.emplace(newBloon.get());
- newBloon->addComponent<Type>(BLOON_T);
- newBloon->addComponent<Kind>(i<2?ZOMG:DDT);
- if (fortifiedP)
- newBloon->addComponent<Fortified>();
- if(i>=2)
- newBloon->addComponent<Camo>();
- newBloon->addComponent<Lives>(getLives<TOTAL_LIVES>(newBloon));
- newBloon->addComponent<Position>(gameData.startingPoint);
- newBloon->addComponent<PathIndex>(0);
- newBloon->getComponent<PathIndex>()->progress =
- i < 2 ? std::fmaxf(0, pathIndex.progress - 20 + 10 * (i % 2)) : std::fminf(
- gameData.path.size() - 1, pathIndex.progress + 30 - 10 * (i % 3));
- surface = gameData.assets[getSurfaceName(newBloon)];
- newBloon->addComponent<Range>(std::max(surface->w / 6, surface->h / 6));
- newBloon->addComponent<Visibility>(gameData.renderer, surface, SDL_Rect{0, 0, surface->w / 3, 0});
- newBloons.emplace_back(newBloon);
- damageBloon(newBloon, shot, i == 0 ? ceilf(damage / 5.0) : damage - ceilf(damage / 5.0) * 4,
- gameData, newBloons);
- }
- bloon->addComponent<RemoveEntityEvent>();
- break;
- } else {
- surface = gameData.assets[getSurfaceName(bloon)];
- visibility.loadTexture(gameData.renderer, surface);
+ damageBloon(newBloon, shot, i == 0 ? ceilf(damage / 5.0) : damage - ceilf(damage / 5.0) * 4,
+ gameData, newBloons);
}
break;
}
}
}
void DamageSystem::update(Entities *layers, GameData &gameData) {
Entities newBloons;
for (auto &bloon: layers[BLOONS_LAYER]) {
if (auto damageEventP = bloon->getComponent<DamageEvent>()) {
auto &damageEvent = *damageEventP;
auto &lives = bloon->getComponent<Lives>()->value;
- if (lives < damageEvent.damage)
+ if (lives < damageEvent.damage) {
+ gameData.money += getBloonProperty<YIELD>(bloon);
bloon->addComponent<RemoveEntityEvent>();
- else {
+ } else {
damageBloon(bloon, damageEvent.shot, damageEvent.damage, gameData, newBloons);
bloon->removeComponent<DamageEvent>();
}
}
}
layers[BLOONS_LAYER] += newBloons;
}
diff --git a/src/systems/RenderSystem.cpp b/src/systems/RenderSystem.cpp
index 73e7b96..bc0b5db 100644
--- a/src/systems/RenderSystem.cpp
+++ b/src/systems/RenderSystem.cpp
@@ -1,72 +1,76 @@
//
// Created by Ido Mozes on 23/06/2019.
//
#include "RenderSystem.h"
-#include "../components/Visibility.h"
-#include "../components/Position.h"
void RenderSystem::init(GameData &gameData) {
if (gameData.window != nullptr)
SDL_DestroyWindow(gameData.window);
if (gameData.renderer != nullptr)
SDL_DestroyRenderer(gameData.renderer);
gameData.window = SDL_CreateWindow("BloonsTD", SDL_WINDOWPOS_CENTERED,
SDL_WINDOWPOS_CENTERED, /* NOLINT(hicpp-signed-bitwise)*/
int((MAP_WIDTH + SIDEBAR_WIDTH + MENU_WIDTH) * gameData.mapScale),
int(MAP_HEIGHT * gameData.mapScale),
gameData.fullscreen ? SDL_WINDOW_FULLSCREEN : 0);
gameData.renderer = SDL_CreateRenderer(gameData.window, -1, 0);
SDL_SetRenderDrawColor(gameData.renderer, 255, 255, 255, 255);
- SDL_RenderSetScale(gameData.renderer, gameData.mapScale, gameData.mapScale);
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "2");
+ gameData.font = FC_CreateFont();
+ FC_LoadFont(gameData.font, gameData.renderer, "../assets/LuckiestGuy-Regular.ttf", 12 * gameData.mapScale,
+ FC_MakeColor(0, 0, 0, 255), TTF_STYLE_NORMAL);
}
void RenderSystem::update(Entities *layers, GameData &gameData) {
SDL_RenderClear(gameData.renderer);
for (int i = 0; i < N_LAYERS; ++i) {
if (i == SEQUENCES_LAYER)
continue;
for (auto &entity: layers[i]) {
auto rangeShadowP = entity->getComponent<RangeShadow>();
auto &currentEntity = rangeShadowP ? rangeShadowP->entity : entity;
if (auto visibilityP = currentEntity->getComponent<Visibility>()) {
auto &visibility = *visibilityP;
SDL_Rect *dstRect = visibility.getDstRect();
- SDL_Rect newDstRect = {dstRect->x, dstRect->y, dstRect->w, dstRect->h};
+ SDL_Rect newDstRect = {int(dstRect->x * gameData.mapScale), int(dstRect->y * gameData.mapScale),
+ int(dstRect->w * gameData.mapScale), int(dstRect->h * gameData.mapScale)};
SDL_Point entityCenter;
auto positionP = currentEntity->getComponent<Position>();
if (positionP) {
auto &position = *positionP;
- entityCenter.x = position.value.X + SIDEBAR_WIDTH;
- entityCenter.y = position.value.Y;
- newDstRect.x = int(position.value.X + SIDEBAR_WIDTH - newDstRect.w / 2.0);
- newDstRect.y = int(position.value.Y - newDstRect.h / 2.0);
+ entityCenter.x = (position.value.X + SIDEBAR_WIDTH) * gameData.mapScale;
+ entityCenter.y = position.value.Y * gameData.mapScale;
+ newDstRect.x = int((position.value.X + SIDEBAR_WIDTH) * gameData.mapScale - newDstRect.w / 2.0);
+ newDstRect.y = int(position.value.Y * gameData.mapScale - newDstRect.h / 2.0);
} else {
- entityCenter.x = int(dstRect->x + dstRect->w / 2.0);
- entityCenter.y = int(dstRect->y + dstRect->h / 2.0);
+ entityCenter.x = int(dstRect->x * gameData.mapScale + (dstRect->w * gameData.mapScale) / 2.0);
+ entityCenter.y = int(dstRect->y * gameData.mapScale + (dstRect->h * gameData.mapScale) / 2.0);
}
if (currentEntity != entity) {
auto draggableP = currentEntity->getComponent<Draggable>();
bool isRed = draggableP ? !draggableP->isPlaceable : false;
float range = currentEntity->getComponent<Range>()->value;
- filledCircleRGBA(gameData.renderer, entityCenter.x, entityCenter.y, range, isRed ? 255 : 0, 0, 0,
+ filledCircleRGBA(gameData.renderer, entityCenter.x, entityCenter.y, range* gameData.mapScale, isRed ? 255 : 0, 0, 0,
100);
- aacircleRGBA(gameData.renderer, entityCenter.x, entityCenter.y, range, isRed ? 255 : 0, 0, 0, 150);
+ aacircleRGBA(gameData.renderer, entityCenter.x, entityCenter.y, range* gameData.mapScale, isRed ? 255 : 0, 0, 0, 150);
}
if (entity == currentEntity) {
SDL_RenderCopyEx(gameData.renderer, visibility.getTexture(), nullptr, &newDstRect, visibility.angle,
nullptr, SDL_FLIP_NONE);
}
}
}
}
+ FC_Draw(gameData.font, gameData.renderer, (MAP_WIDTH+SIDEBAR_WIDTH + 10) * gameData.mapScale, 10 * gameData.mapScale,
+ "Cash: %s", std::to_string(gameData.money).c_str());
SDL_RenderPresent(gameData.renderer);
+
}
diff --git a/src/systems/ShotsSpawnSystem.cpp b/src/systems/ShotsSpawnSystem.cpp
index 2a69003..82847cf 100644
--- a/src/systems/ShotsSpawnSystem.cpp
+++ b/src/systems/ShotsSpawnSystem.cpp
@@ -1,71 +1,82 @@
//
// Created by Ido Mozes on 11/07/2019.
//
#include "ShotsSpawnSystem.h"
void ShotsSpawnSystem::update(Entities *layers, GameData &gameData) {
for (auto &entity: layers[TOWERS_LAYER]) {
auto[kind, shotKind, towerRange, towerPosition, strategy, attackSpeed, pierce, damage, distance, visibility] =
entity->getComponents<Kind, ShotKind, Range, Position, Strategy, AttackSpeed, Pierce, Damage, Distance, Visibility>().value();
float minDistance = MAP_HEIGHT + MAP_WIDTH;
float minProgress = gameData.path.size();
float maxProgress = -1;
Entity *closestBloon = nullptr, *firstBloon = nullptr, *lastBloon = nullptr;
for (auto &gameEntity: layers[BLOONS_LAYER]) {
float distance;
if (gameEntity->getComponent<Type>()->value == BLOON_T) {
auto[bloonRange, bloonPosition, pathIndex] = gameEntity->getComponents<Range, Position, PathIndex>().value();
distance =
twoPointsDistance(bloonPosition.value, towerPosition.value) - bloonRange.value;
if (distance > towerRange.value)
continue;
if (distance < minDistance) {
minDistance = distance;
closestBloon = gameEntity.get();
}
if (pathIndex.progress < minProgress) {
minProgress = pathIndex.progress;
lastBloon = gameEntity.get();
}
if (pathIndex.progress > maxProgress) {
maxProgress = pathIndex.progress;
firstBloon = gameEntity.get();
}
}
}
if (closestBloon or firstBloon or lastBloon) {
Entity *target;
switch (strategy.value) {
case CLOSEST:
target = closestBloon;
break;
case FIRST:
target = firstBloon;
break;
case LAST:
target = lastBloon;
break;
}
int amount = attackSpeed.getAmountReady();
float angle = twoPointsAngle(towerPosition.value, target->getComponent<Position>()->value);
for (int i = 0; i < amount; ++i) {
- EntityP shot(new Entity());
- shot->addComponent<Position>(towerPosition.value.X, towerPosition.value.Y);
- shot->addComponent<Type>(SHOT_T);
- shot->addComponent<Kind>(shotKind.value);
- auto[velocityX, velocityY] = polarToCartesian(angle, getSpeed(shot));
- shot->addComponent<Velocity>(velocityX, velocityY);
- shot->addComponent<Range>(5);
- shot->addComponents(pierce, damage, distance);
- shot->addComponent<PoppedBloons>();
- SDL_Surface *surface = gameData.assets[getSurfaceName(shot)];
- shot->addComponent<Visibility>(gameData.renderer, surface, SDL_Rect{0, 0, surface->w / 25},
- radToDeg(angle));
- layers[SHOTS_LAYER].emplace_back(shot);
+ switch (shotKind.value){
+ case DART:{
+ EntityP shot(new Entity());
+ shot->addComponent<Position>(towerPosition.value.X, towerPosition.value.Y);
+ shot->addComponent<Type>(SHOT_T);
+ shot->addComponent<Kind>(shotKind.value);
+ auto[velocityX, velocityY] = polarToCartesian(angle, getSpeed(shot));
+ shot->addComponent<Velocity>(velocityX, velocityY);
+ shot->addComponent<Range>(5);
+ shot->addComponents(pierce, damage, distance);
+ shot->addComponent<PoppedBloons>();
+ SDL_Surface *surface = gameData.assets[getSurfaceName(shot)];
+ shot->addComponent<Visibility>(gameData.renderer, surface, SDL_Rect{0, 0, surface->w / 25},
+ radToDeg(angle));
+ layers[SHOTS_LAYER].emplace_back(shot);
+ break;
+ }
+ case GUN:
+ {
+ target->addComponent<DamageEvent>(damage.value,EntityP(nullptr));
+ }
+
+ }
+
}
visibility.angle = radToDeg(angle) + 90;
} else
attackSpeed.recharge();
}
}
\ No newline at end of file

File Metadata

Mime Type
text/x-diff
Expires
Fri, Sep 12, 11:15 PM (1 d, 14 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
42969
Default Alt Text
(151 KB)

Event Timeline