diff --git a/.gitignore b/.gitignore index 336790b..86a19f5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,16 @@ -.cache/clangd/index -bin -maps/fencer.tiled-session -fencer -build/obj/Debug -build/Makefile -Makefile +.vscode +.cache +compile_commands/ +bin/ +build/ +intermediate/ +packages/ +*.vcxproj +*.vcxproj.user compile_commands.json +Makefile +.vs +.idea +*.sln +.kdev4 +fencer.kdev4 diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..2f90ad3 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "core/src/utils"] + path = core/src/utils + url = git@git.saragerretsen.nl:Sara/cutes.git diff --git a/Build.lua b/Build.lua new file mode 100644 index 0000000..c40b8b0 --- /dev/null +++ b/Build.lua @@ -0,0 +1,25 @@ +workspace "Fencer-Template" + architecture "x64" + configurations { "Debug", "Release", "Dist" } + language "C" + startproject "Game" + + filter "system:windows" + defines { "SDL_MAIN_HANDLED", "_CRT_SECURE_NO_WARNINGS" } + buildoptions { "/EHsc", "/Zc:preprocessor" } + + OutputDir = "%{cfg.system}-%{cfg.architecture}/%{cfg.buildcfg}" + + group "Core" + include "core/Build-Core.lua" + group "" + + libdirs { + os.findlib("SDL2"), + os.findlib("SDL2_image"), + os.findlib("SDL2_ttf"), + os.findlib("m"), + os.findlib("cJSON") + } + + include "game/Build-Game.lua" diff --git a/SDL2.dll b/SDL2.dll new file mode 100644 index 0000000..e26bcb1 Binary files /dev/null and b/SDL2.dll differ diff --git a/SDL2.lib b/SDL2.lib new file mode 100644 index 0000000..99c5321 Binary files /dev/null and b/SDL2.lib differ diff --git a/SDL2_image.dll b/SDL2_image.dll new file mode 100644 index 0000000..277e981 Binary files /dev/null and b/SDL2_image.dll differ diff --git a/SDL2_image.lib b/SDL2_image.lib new file mode 100644 index 0000000..b29fee0 Binary files /dev/null and b/SDL2_image.lib differ diff --git a/SDL2main.lib b/SDL2main.lib new file mode 100644 index 0000000..241e7c3 Binary files /dev/null and b/SDL2main.lib differ diff --git a/SDL2test.lib b/SDL2test.lib new file mode 100644 index 0000000..1fa2ecb Binary files /dev/null and b/SDL2test.lib differ diff --git a/core/Build-Core.lua b/core/Build-Core.lua new file mode 100644 index 0000000..9e6f104 --- /dev/null +++ b/core/Build-Core.lua @@ -0,0 +1,35 @@ +project "Engine-Core" + kind "StaticLib" + language "C" + targetdir "bin/%{cfg.buildcfg}" + staticruntime "off" + + defines { "VMATH_SDL" } + + files { "src/**.c" } + includedirs { "src/" } + includedirs { "src/utils" } + + targetdir ( "../bin/" .. OutputDir .. "/%{prj.name}" ) + objdir ( "../intermediate/" .. OutputDir .. "/%{prj.name}" ) + + filter "system:windows" + systemversion "latest" + defines {} + + filter "configurations:Debug" + defines { "DEBUG" } + runtime "Debug" + symbols "On" + + filter "configurations:Release" + defines { "RELEASE" } + runtime "Release" + optimize "On" + symbols "On" + + filter "configurations:Dist" + defines { "DIST" } + runtime "Release" + optimize "On" + symbols "Off" diff --git a/core/src/animation_sprite.c b/core/src/animation_sprite.c new file mode 100644 index 0000000..e9fac89 --- /dev/null +++ b/core/src/animation_sprite.c @@ -0,0 +1,114 @@ +#include "animation_sprite.h" +#include "debug.h" +#include "program.h" +#include "list.h" + +struct AnimationSprite { + Spritesheet *sheet; + Sprite *sprite_target; + + AnimationSpriteLoopMode loop_mode; + + float frame_interval; + float start_time; + + size_t event_index; + List events; +}; + +AnimationSprite *animation_sprite_new(Sprite *target_sprite, Spritesheet *sheet, + float framerate, AnimationSpriteLoopMode loop_mode, + AnimationEvent *events, size_t event_count) { + AnimationSprite *self = malloc(sizeof(AnimationSprite)); + ASSERT_RETURN(self != NULL, NULL, "Failed to allocate memory for AnimationSprite"); + *self = (AnimationSprite){ + .sheet = sheet, + .frame_interval = 1.0f / framerate, + .loop_mode = loop_mode, + .start_time = game_time(), + .sprite_target = target_sprite, + .event_index = 0, + .events = list_with_len(sizeof(AnimationEvent), event_count), + }; + if(event_count > 0) { + memcpy(self->events.data, events, sizeof(AnimationEvent) * event_count); + self->events.len = event_count; + } + return self; +} + +void animation_sprite_destroy(AnimationSprite *self) { + spritesheet_destroy(self->sheet); + free(self); +} + +void animation_sprite_reset(AnimationSprite *self) { + self->start_time = game_time(); +} + +void animation_sprite_play_from(AnimationSprite *self, float normalized_time) { + self->start_time = game_time() - normalized_time * animation_sprite_get_length(self); +} + +void animation_sprite_draw(AnimationSprite *self, Transform *transform) { + const size_t frame_count = spritesheet_get_tile_count(self->sheet); + const float time = game_time() - self->start_time; + size_t frame = (size_t)(time / self->frame_interval); + switch(self->loop_mode) { + case LoopMode_Stop: + if(frame >= frame_count) + frame = frame_count - 1; + case LoopMode_Hide: + if(frame >= frame_count) + return; + else + break; + case LoopMode_Loop: + frame %= frame_count; + break; + case LoopMode_PingPong: + frame %= frame_count * 2; + if(frame >= frame_count) + frame = frame_count - (frame - frame_count); + return; + } + + sprite_set_spritesheet(self->sprite_target, self->sheet); + sprite_set_tile(self->sprite_target, frame); + sprite_draw(self->sprite_target, *transform); +} + +void animation_sprite_update_events(AnimationSprite *self) { + const float current_time = game_time() - self->start_time; + for(;self->event_index <= self->events.len; ++self->event_index) { + AnimationEvent *event = list_at_as(AnimationEvent, &self->events, self->event_index); + if(event->time < current_time) + return; + else + event->fn(event->target, event->arg.data); + } +} + +float animation_sprite_get_length(AnimationSprite *self) { + return (float)spritesheet_get_tile_count(self->sheet) * self->frame_interval; +} + +void animation_sprite_set_framerate(AnimationSprite *self, float framerate) { + self->frame_interval = 1.0f / framerate; +} + +float animation_sprite_get_framerate(const AnimationSprite *self) { + return 1.0f / self->frame_interval; +} + +Sprite *animation_sprite_get_sprite(AnimationSprite *self) { + return self->sprite_target; +} + +float animation_sprite_get_time(AnimationSprite *self) { + return game_time() - self->start_time; +} + +float animation_sprite_get_time_normalized(AnimationSprite *self) { + return animation_sprite_get_time(self) / animation_sprite_get_length(self); +} diff --git a/core/src/animation_sprite.h b/core/src/animation_sprite.h new file mode 100644 index 0000000..0e2fe01 --- /dev/null +++ b/core/src/animation_sprite.h @@ -0,0 +1,42 @@ +#ifndef _fencer_animation_sprite_h +#define _fencer_animation_sprite_h + +#include "drop.h" +#include "sprite.h" +#include "spritesheet.h" + +typedef enum AnimationSpriteLoopMode { + LoopMode_Stop, + LoopMode_Hide, + LoopMode_Loop, + LoopMode_PingPong, +} AnimationSpriteLoopMode; + +typedef void (*AnimationEventFn)(void *object, void *arg); + +typedef struct AnimationEvent { + float time; + void *target; + Drop arg; + AnimationEventFn fn; +} AnimationEvent; + +typedef struct AnimationSprite AnimationSprite; + +extern AnimationSprite *animation_sprite_new(Sprite *target_sprite, Spritesheet *sheet, float framerate, AnimationSpriteLoopMode loop_mode, AnimationEvent *events, size_t event_count); +extern void animation_sprite_destroy(AnimationSprite *self); + +extern void animation_sprite_reset(AnimationSprite *self); +extern void animation_sprite_play_from(AnimationSprite *self, float normalized_time); +extern void animation_sprite_draw(AnimationSprite *self, Transform *transform); + +extern void animation_sprite_update_events(AnimationSprite *self); + +extern float animation_sprite_get_length(AnimationSprite *self); +extern void animation_sprite_set_framerate(AnimationSprite *self, float framerate); +extern float animation_sprite_get_framerate(const AnimationSprite *self); +extern Sprite *animation_sprite_get_sprite(AnimationSprite *self); +extern float animation_sprite_get_time(AnimationSprite *self); +extern float animation_sprite_get_time_normalized(AnimationSprite *self); + +#endif // !_fencer_animation_sprite_h diff --git a/src/asset.h b/core/src/asset.h similarity index 91% rename from src/asset.h rename to core/src/asset.h index c113ed5..08cbc3f 100644 --- a/src/asset.h +++ b/core/src/asset.h @@ -19,11 +19,11 @@ typedef struct Asset { } Asset; #define impl_Asset_for(T, get_id_f, set_id_f)\ -static inline Asset T##_as_Asset(T* x) {\ +Asset T##_as_Asset(T* x) {\ TC_FN_TYPECHECK(asset_id, get_id_f, T*);\ TC_FN_TYPECHECK(void, set_id_f, T*, asset_id);\ TC_FN_TYPECHECK(Drop, T##_as_Drop, T*);\ - static IAsset tc = (IAsset){\ + static IAsset const tc = {\ .get_id = (asset_id(*const)(void*)) get_id_f,\ .set_id = (void(*const)(void*,asset_id)) set_id_f,\ };\ diff --git a/src/assets.c b/core/src/assets.c similarity index 94% rename from src/assets.c rename to core/src/assets.c index 6473d2c..0f90a1d 100644 --- a/src/assets.c +++ b/core/src/assets.c @@ -10,16 +10,16 @@ static asset_id _next_id = 0; static size_t file_length(FILE* fp) { - size_t start = ftell(fp); + long start = ftell(fp); fseek(fp, 0, SEEK_END); - size_t r = ftell(fp); + long r = ftell(fp); fseek(fp, start, SEEK_SET); - return r; + return (size_t)r; } static void read_file(FILE* fp, char* out_buffer, size_t out_size) { - size_t start = ftell(fp); + long start = ftell(fp); fread(out_buffer, 1, out_size, fp); fseek(fp, start, SEEK_SET); } @@ -88,15 +88,13 @@ asset_id get_asset_id(void* asset) { } void free_asset(asset_id id) { - Asset* found; - size_t found_index; + Asset* found = NULL; + size_t found_index = _assets.len; for(size_t i = 0; i < _assets.len; ++i) { found = list_at_as(Asset, &_assets, i); if(found->tc->get_id(found->data) == id) { found_index = i; break; - } else { - found = NULL; } } ASSERT_RETURN(found != NULL,, "Attempt to free nonexistent asset."); diff --git a/src/assets.h b/core/src/assets.h similarity index 92% rename from src/assets.h rename to core/src/assets.h index 8f6a11b..579254a 100644 --- a/src/assets.h +++ b/core/src/assets.h @@ -31,8 +31,8 @@ size_t json_array_len(cJSON* array); static inline Vector json_array_to_vector(cJSON* array) { return (Vector) { - cJSON_GetArrayItem(array, 0)->valuedouble, - cJSON_GetArrayItem(array, 1)->valuedouble, + (float)cJSON_GetArrayItem(array, 0)->valuedouble, + (float)cJSON_GetArrayItem(array, 1)->valuedouble, }; } static inline diff --git a/src/behaviour_entity.h b/core/src/behaviour_entity.h similarity index 62% rename from src/behaviour_entity.h rename to core/src/behaviour_entity.h index afe237d..bd7802a 100644 --- a/src/behaviour_entity.h +++ b/core/src/behaviour_entity.h @@ -2,36 +2,40 @@ #define _update_entity_h #include "drop.h" +#include "mirror.h" #include "typeclass_helpers.h" -#include "vmath.h" typedef struct { - void (*const spawn)(void* self, Vector at); void (*const update)(void* self, float dt); void (*const start)(void* self); void (*const draw)(void* self); + long (*const get_depth)(void* self); } IEntityBehaviour; typedef struct { void* data; IEntityBehaviour const* tc; IDrop const* drop; + IMirror const* mirror; } BehaviourEntity; -#define impl_BehaviourEntity_for(T, start_f, update_f, spawn_f, draw_f)\ -static inline BehaviourEntity T##_as_BehaviourEntity(T* x) {\ +#define impl_BehaviourEntity_for(T, start_f, update_f, draw_f, get_depth_f)\ +BehaviourEntity T##_as_BehaviourEntity(T* x) {\ TC_FN_TYPECHECK(void, start_f, T*);\ TC_FN_TYPECHECK(void, update_f, T*, float);\ TC_FN_TYPECHECK(void, draw_f, T*);\ + TC_FN_TYPECHECK(long, get_depth_f, T*);\ static IEntityBehaviour const tc = {\ - .spawn = (void(*const)(void*, Vector)) spawn_f,\ .update = (void(*const)(void*, float)) update_f,\ .start = (void(*const)(void*)) start_f,\ .draw = (void(*const)(void*)) draw_f,\ + .get_depth=(long(*const)(void*)) get_depth_f,\ };\ TC_FN_TYPECHECK(Drop, T##_as_Drop, T*);\ + TC_FN_TYPECHECK(Mirror, T##_as_Mirror, T*);\ IDrop const* drop = T##_as_Drop(x).tc;\ - return (BehaviourEntity){.tc = &tc, .drop = drop, .data = x};\ + IMirror const* mirror = T##_as_Mirror(x).tc;\ + return (BehaviourEntity){.data = x, .tc = &tc, .drop = drop, .mirror = mirror};\ }\ #endif // !_update_entity_h diff --git a/src/camera.c b/core/src/camera.c similarity index 84% rename from src/camera.c rename to core/src/camera.c index 80b7946..c9c896b 100644 --- a/src/camera.c +++ b/core/src/camera.c @@ -21,7 +21,7 @@ SDL_FRect camera_world_to_pixel_rect(Camera* self, SDL_FRect* world_space) { t.scale = OneVector; t = transform_invert(t); - Vector tl = {world_space->x + (self->fov / 2.0), world_space->y + (_camera_height(self) / 2.0)}; + Vector tl = {world_space->x + (self->fov / 2.0f), world_space->y + (_camera_height(self) / 2.0f)}; Vector size = {world_space->w, world_space->h}; tl = vmulff(transform_point(&t, tl), g_render_resolution.x / self->fov); @@ -40,7 +40,7 @@ Vector camera_world_to_pixel_point(Camera* self, Vector point) { t.scale = OneVector; t = transform_invert(t); - point = (Vector){point.x + (self->fov / 2.0), point.y + (_camera_height(self) / 2.0)}; + point = (Vector){point.x + (self->fov / 2.0f), point.y + (_camera_height(self) / 2.0f)}; return vmulff(transform_point(&t, point), g_render_resolution.x / self->fov); } diff --git a/src/camera.h b/core/src/camera.h similarity index 100% rename from src/camera.h rename to core/src/camera.h diff --git a/core/src/collider.c b/core/src/collider.c new file mode 100644 index 0000000..ef64b6b --- /dev/null +++ b/core/src/collider.c @@ -0,0 +1,77 @@ +#include "collider.h" +#include "collision.h" +#include "debug.h" + +struct Collider { + Shape* shape; + PhysicsEntity owner; + RigidBody* body; + PhysicsMask layers; + PhysicsMask mask; + int overlap; +}; + +Collider* collider_new(PhysicsEntity owner, Shape* shape, int overlap, PhysicsMask layers, PhysicsMask mask) { + Collider* self = malloc(sizeof(Collider)); + ASSERT_RETURN(self != NULL, NULL, "Failed to allocate space for Collider"); + *self = (Collider) { + .shape = shape, + .owner = owner, + .body = owner.tc->get_rigidbody(owner.data), + .layers = layers, + .mask = mask, + .overlap = overlap, + }; + rigidbody_add_collider(self->body, self); + return self; +} + +void collider_destroy(Collider* self) { + shape_destroy(self->shape); + rigidbody_remove_collider(self->body, self); + free(self); +} + +PhysicsQuery collider_to_query(Collider* self) { + return (PhysicsQuery) { + .shape = self->shape, + .transform = rigidbody_get_transform(self->body), + .mask = self->mask + }; +} + +Shape* collider_get_shape(Collider* self) { + return self->shape; +} + +RigidBody* collider_get_rigidbody(Collider* self) { + return self->body; +} + +int collider_is_overlap(Collider* self) { + return self->overlap; +} + +void collider_set_overlap(Collider* self, int value) { + self->overlap = value; +} + +PhysicsMask collider_get_mask(const Collider* self) { + return self->mask; +} + +void collider_set_mask(Collider* self, PhysicsMask mask) { + self->mask = mask; +} + +PhysicsMask collider_get_layers(const Collider* self) { + return self->layers; +} + +void collider_set_layers(Collider* self, PhysicsMask layers) { + self->layers = layers; +} + +PhysicsEntity collider_get_owner(Collider* self) { + return self->owner; +} diff --git a/core/src/collider.h b/core/src/collider.h new file mode 100644 index 0000000..8199d93 --- /dev/null +++ b/core/src/collider.h @@ -0,0 +1,27 @@ +#ifndef _fencer_collider_h +#define _fencer_collider_h + +#include "shape.h" +#include "rigidbody.h" + +typedef struct Collider Collider; + +extern Collider* collider_new(PhysicsEntity owner, Shape* shape, int overlap, PhysicsMask layers, PhysicsMask mask); +extern void collider_destroy(Collider* self); +extern PhysicsQuery collider_to_query(Collider* self); + +extern Shape* collider_get_shape(Collider* self); +extern RigidBody* collider_get_rigidbody(Collider* self); + +extern int collider_is_overlap(Collider* self); +extern void collider_set_overlap(Collider* self, int value); + +extern PhysicsMask collider_get_mask(const Collider* self); +extern void collider_set_mask(Collider* self, PhysicsMask mask); + +extern PhysicsMask collider_get_layers(const Collider* self); +extern void collider_set_layers(Collider* self, PhysicsMask layers); + +extern PhysicsEntity collider_get_owner(Collider* self); + +#endif // !_fencer_collider_h diff --git a/src/collision.c b/core/src/collision.c similarity index 56% rename from src/collision.c rename to core/src/collision.c index 11968b5..0de7149 100644 --- a/src/collision.c +++ b/core/src/collision.c @@ -1,6 +1,7 @@ #include "collision.h" #include "vmath.h" #include "rigidbody.h" +#include "collider.h" // ===================================================== // Shape overlap test using the separating axis theorem @@ -9,9 +10,9 @@ typedef struct Range {float min; Vector minpoint; float max; Vector maxpoint; } Range; static -Range _internal_collision_get_range_on_axis(PhysicsEntity self, Vector axis) { - Transform* transform = self.transformable->get_transform(self.data); - Shape* shape = self.tc->get_shape(self.data); +Range _internal_collision_get_range_on_axis(PhysicsQuery self, Vector axis) { + Transform* transform = self.transform; + Shape* shape = self.shape; Vector point = shape_get_point_transformed(shape, 0, *transform); float dot = vdotf(axis, point); Range range = {dot, point, dot, point}; @@ -33,7 +34,7 @@ Range _internal_collision_get_range_on_axis(PhysicsEntity self, Vector axis) { } static -Vector _internal_collision_overlap_on_axis(PhysicsEntity self, PhysicsEntity other, Vector axis, Vector* out_point) { +Vector _internal_collision_overlap_on_axis(PhysicsQuery self, PhysicsQuery other, Vector axis, Vector* out_point) { Range a_range = _internal_collision_get_range_on_axis(self, axis); Range b_range = _internal_collision_get_range_on_axis(other, axis); @@ -49,10 +50,20 @@ Vector _internal_collision_overlap_on_axis(PhysicsEntity self, PhysicsEntity oth } static -int _internal_collision_get_overlap(PhysicsEntity self, PhysicsEntity other, Collision* out) { +int _internal_collision_get_collisions(Collider* self, Collider* other, Collision* out) { // get components used - Shape* self_shape = self.tc->get_shape(self.data); - Transform* self_transform = self.transformable->get_transform(self.data); + Shape* self_shape = collider_get_shape(self); + Transform* self_transform = rigidbody_get_transform(collider_get_rigidbody(self)); + PhysicsQuery self_query = { + .shape = self_shape, + .transform = self_transform, + .mask = 0x0 // not used + }; + PhysicsQuery other_query = { + .shape = collider_get_shape(other), + .transform = rigidbody_get_transform(collider_get_rigidbody(other)), + .mask = 0x0 // not used + }; // the shortest distance to solve collision found so far Vector shortest_escape = InfinityVector; @@ -67,14 +78,14 @@ int _internal_collision_get_overlap(PhysicsEntity self, PhysicsEntity other, Col // the next point on the line size_t next_index = (point_index + 1) % self_point_count; // get the two points defining the collision edge - Vector edge_lhs = shape_get_point_transformed(self.tc->get_shape(self.data), point_index, *self_transform); - Vector edge_rhs = shape_get_point_transformed(self.tc->get_shape(self.data), next_index, *self_transform); + Vector edge_lhs = shape_get_point_transformed(self_shape, point_index, *self_transform); + Vector edge_rhs = shape_get_point_transformed(self_shape, next_index, *self_transform); // the direction of the line Vector normal = vnormalizedf(vperpendicularf(vsubf(edge_rhs, edge_lhs))); Vector overlap_point; // the smallest escape vector on this axis - Vector escape = _internal_collision_overlap_on_axis(self, other, normal, &overlap_point); + Vector escape = _internal_collision_overlap_on_axis(self_query, other_query, normal, &overlap_point); float dot = vdotf(vinvf(normal), escape); if(dot <= 0.0) { return 0; @@ -86,11 +97,11 @@ int _internal_collision_get_overlap(PhysicsEntity self, PhysicsEntity other, Col } } - RigidBody* rba = self.tc->get_rigidbody(self.data); - RigidBody* rbb = other.tc->get_rigidbody(other.data); + RigidBody* rba = collider_get_rigidbody(self); + RigidBody* rbb = collider_get_rigidbody(other); const Vector velocity = vsubf(rigidbody_get_velocity(rba), rigidbody_get_velocity(rbb)); const Vector normal = vnormalizedf(shortest_escape); - Vector world_point = _internal_collision_get_range_on_axis(self, normal).minpoint; + Vector world_point = _internal_collision_get_range_on_axis(self_query, normal).minpoint; *out = (Collision) { .other = other, @@ -108,9 +119,13 @@ int _internal_collision_get_overlap(PhysicsEntity self, PhysicsEntity other, Col return !veqf(shortest_escape, ZeroVector); } -Collision collision_invert(Collision collision_a, PhysicsEntity a) { - Vector world_point = _internal_collision_get_range_on_axis(collision_a.other, collision_a.normal).maxpoint; - RigidBody* body = collision_a.other.tc->get_rigidbody(collision_a.other.data); +Collision collision_invert(Collision collision_a, Collider* a) { + RigidBody* body = collider_get_rigidbody(a); + Shape* shape = collider_get_shape(a); + Transform* transform = rigidbody_get_transform(body); + + Vector world_point = _internal_collision_get_range_on_axis((PhysicsQuery){.shape = shape, .transform = transform }, collision_a.normal).maxpoint; + return (Collision){ .other = a, .point = inverse_transform_point(rigidbody_get_transform(body), world_point), @@ -122,10 +137,12 @@ Collision collision_invert(Collision collision_a, PhysicsEntity a) { }; } -int collision_check(PhysicsEntity a, PhysicsEntity b, Collision* out_a, Collision* out_b) { +int collision_check(Collider* a, Collider* b, Collision* out_a, Collision* out_b) { + if(!(collider_get_layers(a) & collider_get_mask(b)) || !(collider_get_mask(a) & collider_get_layers(b))) + return 0; Collision collision_a, collision_b; - int collision_a_overlaps = _internal_collision_get_overlap(a, b, &collision_a); - int collision_b_overlaps = _internal_collision_get_overlap(b, a, &collision_b); + int collision_a_overlaps = _internal_collision_get_collisions(a, b, &collision_a); + int collision_b_overlaps = _internal_collision_get_collisions(b, a, &collision_b); if(!collision_a_overlaps || !collision_b_overlaps) return 0; @@ -139,3 +156,34 @@ int collision_check(PhysicsEntity a, PhysicsEntity b, Collision* out_a, Collisio *out_b = collision_b; return (collision_b_overlaps << 1) | collision_a_overlaps; } + +static +int _internal_overlap_check(PhysicsQuery a, PhysicsQuery b) { + Shape* shape = a.shape; + Transform* transform = a.transform; + const size_t shape_point_count = shape_get_points_count(shape); + + for(size_t point_index = 0; point_index < shape_point_count; ++point_index) { + // the next point on the line + size_t next_index = (point_index + 1) % shape_point_count; + // get the two points defining the collision edge + Vector edge_lhs = shape_get_point_transformed(shape, point_index, *transform); + Vector edge_rhs = shape_get_point_transformed(shape, next_index, *transform); + // the direction of the line + Vector normal = vnormalizedf(vperpendicularf(vsubf(edge_rhs, edge_lhs))); + + Vector overlap_point; + // the smallest escape vector on this axis + Vector escape = _internal_collision_overlap_on_axis(a, b, normal, &overlap_point); + float dot = vdotf(vinvf(normal), escape); + if(dot <= 0.0) { + return 0; + } + } + return 1; +} + +int overlap_check(PhysicsQuery query, Collider* collider) { + PhysicsQuery collider_query = collider_to_query(collider); + return (query.mask & collider_get_layers(collider)) != 0 && (_internal_overlap_check(query, collider_query) || _internal_overlap_check(collider_query, query)); +} diff --git a/core/src/collision.h b/core/src/collision.h new file mode 100644 index 0000000..c7e3efa --- /dev/null +++ b/core/src/collision.h @@ -0,0 +1,13 @@ +#ifndef _fencer_collision_h +#define _fencer_collision_h + +#include "physics_entity.h" +#include "physics.h" + +typedef struct Collider Collider; + +extern Collision collision_invert(Collision src, Collider* new_other); +extern int collision_check(Collider* a, Collider* b, Collision* out_a, Collision* out_b); +extern int overlap_check(PhysicsQuery query, Collider* collider); + +#endif // !_fencer_collision_h diff --git a/src/game_world.c b/core/src/game_world.c similarity index 61% rename from src/game_world.c rename to core/src/game_world.c index 8dfa86f..c7cc695 100644 --- a/src/game_world.c +++ b/core/src/game_world.c @@ -12,7 +12,7 @@ static inline size_t _internal_find_index_for_entity(void* data, const List* list) { for(size_t i = 0; i < _game_entities.len; ++i) { BehaviourEntity* entity = list_at_as(BehaviourEntity, &_game_entities, i); - if(entity->data == entity) { + if(entity->data == data) { return i; } } @@ -22,10 +22,10 @@ size_t _internal_find_index_for_entity(void* data, const List* list) { static inline void _internal_clear_removed() { - list_foreach(size_t*, index, &_remove_queue) { - BehaviourEntity* entity = list_at_as(BehaviourEntity, &_game_entities, *index); + list_foreach(BehaviourEntity*, entity, &_remove_queue) { + size_t index = _internal_find_index_for_entity(entity->data, &_game_entities); entity->drop->drop(entity->data); - list_erase(&_game_entities, *index); + list_erase(&_game_entities, index); } list_empty(&_remove_queue); } @@ -42,7 +42,7 @@ void _internal_process_new() { void game_world_init() { _game_entities = list_from_type(BehaviourEntity); _add_queue = list_from_type(BehaviourEntity); - _remove_queue = list_from_type(size_t); + _remove_queue = list_from_type(BehaviourEntity); } void game_world_close() { @@ -58,11 +58,9 @@ void game_world_add_entity(BehaviourEntity entity) { list_add(&_add_queue, &entity); } -void game_world_remove_entity(void* entity) { - size_t index = _internal_find_index_for_entity(entity, &_game_entities); - if(index != _game_entities.len) { - list_add(&_remove_queue, &index); - } +void game_world_destroy_entity(BehaviourEntity entity) { + if(list_contains(&_remove_queue, &entity) == _remove_queue.len) + list_add(&_remove_queue, &entity); } void game_world_update() { @@ -73,8 +71,21 @@ void game_world_update() { _internal_clear_removed(); } -void game_word_draw() { - list_foreach(BehaviourEntity*, entity, &_game_entities) { +static +int _internal_compare_depth(const BehaviourEntity* a, const BehaviourEntity* b) { + return b->tc->get_depth(b->data) - a->tc->get_depth(a->data); +} + +#define AS_COMPARISON(__FN) ((int(*)(const void*, const void*))__FN) + +void game_world_draw() { + List draw_order = list_copy(&_game_entities); + + qsort(_game_entities.data, _game_entities.len, _game_entities.element_size, + AS_COMPARISON(_internal_compare_depth)); + list_foreach(BehaviourEntity*, entity, &draw_order) { entity->tc->draw(entity->data); } + + list_empty(&draw_order); } diff --git a/src/game_world.h b/core/src/game_world.h similarity index 83% rename from src/game_world.h rename to core/src/game_world.h index d35c7d2..a963d93 100644 --- a/src/game_world.h +++ b/core/src/game_world.h @@ -7,7 +7,7 @@ extern void game_world_init(); extern void game_world_close(); extern void game_world_add_entity(BehaviourEntity entity); -extern void game_world_remove_entity(void* entity); +extern void game_world_destroy_entity(BehaviourEntity entity); extern void game_world_update(); extern void game_world_draw(); diff --git a/src/input.c b/core/src/input.c similarity index 89% rename from src/input.c rename to core/src/input.c index 8c6d162..cf5c78f 100644 --- a/src/input.c +++ b/core/src/input.c @@ -7,6 +7,7 @@ static List _devices; static inline void _internal_open_keyboard() { InputDevice* keyboard = malloc(sizeof(InputDevice)); + ASSERT_RETURN(keyboard != NULL, , "Failed to allocate space for keyboard input device"); *keyboard = (InputDevice){ .listeners = NULL, .type = InputDevice_KBM, @@ -20,8 +21,9 @@ void _internal_open_keyboard() { } static inline -void _internal_open_controller(size_t id) { +void _internal_open_controller(int id) { InputDevice* device = malloc(sizeof(InputDevice)); + ASSERT_RETURN(device != NULL, , "Failed to allocate space for gamecontroller input device"); *device = (InputDevice) { .listeners = NULL, .type = InputDevice_Gamepad, @@ -42,8 +44,8 @@ void input_init() { _internal_open_keyboard(); // open any controllers already available - const size_t joystick_count = SDL_NumJoysticks(); - for(size_t i = 0; i < joystick_count; ++i) { + const int joystick_count = SDL_NumJoysticks(); + for(int i = 0; i < joystick_count; ++i) { _internal_open_controller(i); } } diff --git a/src/input.h b/core/src/input.h similarity index 94% rename from src/input.h rename to core/src/input.h index fd4ed4c..8d4b026 100644 --- a/src/input.h +++ b/core/src/input.h @@ -10,8 +10,6 @@ struct PlayerInput; -typedef void (*InputDelegateFn)(void* self, InputEvent event); - typedef struct InputListener { void* self; InputDelegateFn fn; diff --git a/src/input_axis.c b/core/src/input_axis.c similarity index 74% rename from src/input_axis.c rename to core/src/input_axis.c index 36ee80f..b299db4 100644 --- a/src/input_axis.c +++ b/core/src/input_axis.c @@ -2,8 +2,45 @@ #include "debug.h" #include "input.h" + +impl_default_Drop_for( + KeyBind +) +impl_InputAxis_for(KeyBind, + keybind_is_changed_by, + keybind_evaluate, + keybind_set_device +) +impl_default_Drop_for( + ControllerAxis +) +impl_InputAxis_for(ControllerAxis, + controlleraxis_is_changed_by, + controlleraxis_evaluate, + controlleraxis_set_device +) + +impl_default_Drop_for( + ControllerButton +) +impl_InputAxis_for(ControllerButton, + controllerbutton_is_changed_by, + controllerbutton_evaluate, + controllerbutton_set_device +) + +impl_Drop_for(CompositeAxis1D, + compositeaxis1d_drop +) +impl_InputAxis_for(CompositeAxis1D, + compositeaxis1d_is_changed_by, + compositeaxis1d_evaluate, + compositeaxis1d_set_device +) + KeyBind* keybind_new(SDL_Scancode key) { KeyBind* self = malloc(sizeof(KeyBind)); + ASSERT_RETURN(self != NULL, NULL, "Failed to allocate space for KeyBind instance"); *self = (KeyBind) { .device = NULL, .scancode = key, @@ -15,7 +52,8 @@ KeyBind* keybind_new(SDL_Scancode key) { int keybind_is_changed_by(KeyBind* self, SDL_Event event) { return self->device->type == InputDevice_KBM && (event.type == SDL_KEYUP || event.type == SDL_KEYDOWN) - && event.key.keysym.scancode == self->scancode; + && event.key.keysym.scancode == self->scancode + && event.key.repeat == 0; } InputEvent keybind_evaluate(KeyBind* self, SDL_Event event) { @@ -31,6 +69,7 @@ void keybind_set_device(KeyBind* self, InputDevice* device) { ControllerAxis* controlleraxis_new(int axis) { ControllerAxis* self = malloc(sizeof(ControllerAxis)); + ASSERT_RETURN(self != NULL, NULL, "Failed to allocate space for ControllerAxis instance"); *self = (ControllerAxis){ .axis = axis, .device = NULL @@ -47,8 +86,7 @@ int controlleraxis_is_changed_by(ControllerAxis* self, SDL_Event event) { } InputEvent controlleraxis_evaluate(ControllerAxis* self, SDL_Event event) { - float result = (float)event.caxis.value / 32767.0; - LOG_INFO("axis %f", result); + float result = (float)event.caxis.value / 32767.0f; return (InputEvent) { .type = InputEvent_Float, .as_float = result @@ -61,6 +99,7 @@ void controlleraxis_set_device(ControllerAxis* self, InputDevice* device) { ControllerButton* controllerbutton_new(int button) { ControllerButton* self = malloc(sizeof(ControllerButton)); + ASSERT_RETURN(self != NULL, NULL, "Failed to allocate space for ControllerButton instance"); *self = (ControllerButton) { .button = button, .device = NULL @@ -88,6 +127,7 @@ void controllerbutton_set_device(ControllerButton* self, InputDevice* device) { CompositeAxis1D* compositeaxis1d_new(InputAxis left, InputAxis right, InputEventType type) { CompositeAxis1D* self = malloc(sizeof(CompositeAxis1D)); + ASSERT_RETURN(self != NULL, NULL, "Failed to allocate space for CompositeAxis1D instance"); *self = (CompositeAxis1D) { .left = left, .right = right, @@ -112,19 +152,19 @@ InputEvent _internal_event_to_type(InputEventType type, InputEvent event) { LOG_ERROR("No (1)"); break; case InputEvent_Bool: - as_float = event.as_bool; + as_float = (float)event.as_bool; break; case InputEvent_Float: as_float = event.as_float; break; case InputEvent_Int: - as_float = event.as_int; + as_float = (float)event.as_int; break; } event.type = type; switch(type) { case InputEvent_Int: - event.as_int = round(as_float); + event.as_int = (int)round(as_float); return event; case InputEvent_Float: event.as_float = as_float; @@ -185,3 +225,17 @@ void compositeaxis1d_drop(CompositeAxis1D* self) { self->right.drop->drop(self->right.data); free(self); } + +CompositeAxis1D* compositeaxis1d_from_keys(SDL_Scancode negative, SDL_Scancode positive) { + return compositeaxis1d_new( + KeyBind_as_InputAxis(keybind_new(negative)), + KeyBind_as_InputAxis(keybind_new(positive)), + InputEvent_Float); +} + +CompositeAxis1D* compositeaxis1d_from_buttons(int negative, int positive) { + return compositeaxis1d_new( + ControllerButton_as_InputAxis(controllerbutton_new(negative)), + ControllerButton_as_InputAxis(controllerbutton_new(positive)), + InputEvent_Float); +} diff --git a/src/input_axis.h b/core/src/input_axis.h similarity index 82% rename from src/input_axis.h rename to core/src/input_axis.h index 57af6c8..6665e07 100644 --- a/src/input_axis.h +++ b/core/src/input_axis.h @@ -39,8 +39,10 @@ typedef struct { IDrop const* drop; } InputAxis; +typedef void (*InputDelegateFn)(void* self, InputEvent event); + #define impl_InputAxis_for(T, is_changed_by_f, evaluate_f, set_device_f)\ -static inline InputAxis T##_as_InputAxis(T* x) {\ +InputAxis T##_as_InputAxis(T* x) {\ TC_FN_TYPECHECK(int, is_changed_by_f, T*, SDL_Event);\ TC_FN_TYPECHECK(struct InputEvent, evaluate_f, T*, SDL_Event);\ TC_FN_TYPECHECK(void, set_device_f, T*, struct InputDevice*);\ @@ -64,12 +66,8 @@ extern int keybind_is_changed_by(KeyBind* self, SDL_Event event); extern struct InputEvent keybind_evaluate(KeyBind* self, SDL_Event); extern void keybind_set_device(KeyBind* self, struct InputDevice* device); -impl_default_Drop_for(KeyBind) -impl_InputAxis_for(KeyBind, - keybind_is_changed_by, - keybind_evaluate, - keybind_set_device -) +decl_typeclass_impl(InputAxis, KeyBind) +decl_typeclass_impl(Drop, KeyBind) typedef struct ControllerAxis { struct InputDevice* device; @@ -81,12 +79,8 @@ extern int controlleraxis_is_changed_by(ControllerAxis* self, SDL_Event event); extern struct InputEvent controlleraxis_evaluate(ControllerAxis* self, SDL_Event event); extern void controlleraxis_set_device(ControllerAxis* self, struct InputDevice* device); -impl_default_Drop_for(ControllerAxis) -impl_InputAxis_for(ControllerAxis, - controlleraxis_is_changed_by, - controlleraxis_evaluate, - controlleraxis_set_device -) +decl_typeclass_impl(InputAxis, ControllerAxis) +decl_typeclass_impl(Drop, ControllerAxis) typedef struct ControllerButton { struct InputDevice* device; @@ -98,12 +92,8 @@ extern int controllerbutton_is_changed_by(ControllerButton* self, SDL_Event even extern struct InputEvent controllerbutton_evaluate(ControllerButton* self, SDL_Event event); extern void controllerbutton_set_device(ControllerButton* self, struct InputDevice* device); -impl_default_Drop_for(ControllerButton) -impl_InputAxis_for(ControllerButton, - controllerbutton_is_changed_by, - controllerbutton_evaluate, - controllerbutton_set_device -) +decl_typeclass_impl(InputAxis, ControllerButton) +decl_typeclass_impl(Drop, ControllerButton) typedef struct CompositeAxis1D { InputAxis left; @@ -117,13 +107,10 @@ extern struct InputEvent compositeaxis1d_evaluate(CompositeAxis1D* self, SDL_Eve extern void compositeaxis1d_set_device(CompositeAxis1D* self, struct InputDevice* device); extern void compositeaxis1d_drop(CompositeAxis1D* self); -impl_Drop_for(CompositeAxis1D, - compositeaxis1d_drop -) -impl_InputAxis_for(CompositeAxis1D, - compositeaxis1d_is_changed_by, - compositeaxis1d_evaluate, - compositeaxis1d_set_device -) +extern CompositeAxis1D* compositeaxis1d_from_keys(SDL_Scancode negative, SDL_Scancode positive); +extern CompositeAxis1D* compositeaxis1d_from_buttons(int negative, int positive); + +decl_typeclass_impl(InputAxis, CompositeAxis1D) +decl_typeclass_impl(Drop, CompositeAxis1D) #endif // !_fencer_input_axis_h diff --git a/core/src/level.c b/core/src/level.c new file mode 100644 index 0000000..86ea7a5 --- /dev/null +++ b/core/src/level.c @@ -0,0 +1,177 @@ +#include "level.h" +#include "game_world.h" +#include "physics_entity.h" +#include "physics_world.h" +#include "strutil.h" +#include "variant.h" +#include "ctype.h" + +static Dictionary internal_deserializers = { + .list = { .data = NULL, .len = 0, .cap = 0, .element_size = 0 }, + .element_size = 0 +}; + +static int is_open_bracket(int c) { return c == '{'; } +static int is_close_bracket(int c) { return c == '}'; } +static int is_colon(int c) { return c == ':'; } +static int ends_parameter(int c) { return c == '}' || c == ','; } + +int level_init() { + internal_deserializers = dictionary_from_type(DeserializeFn); + return 0; +} + +int level_clean() { + dictionary_empty(&internal_deserializers); + return 0; +} + +int level_register_spawner(const char* object_tag, DeserializeFn spawn_function) { + dictionary_set_value(DeserializeFn, &internal_deserializers, object_tag, spawn_function); + return 0; +} + +static inline +void spawn_object(DeserializeFn spawner, Dictionary* args) { + BehaviourEntity entity = spawner(args); + game_world_add_entity(entity); + if(TC_MIRRORS(entity, PhysicsEntity)) + physics_world_add_entity(TC_CAST(entity, PhysicsEntity)); +} + +static inline +long get_until(FILE* fp, char* out_buf, size_t buf_len, CharPredFn predfn) { + if(feof(fp)) return 0; + int c; + char* write = out_buf; + long count = 0; + while(c != EOF) { + // don't overflow + if(out_buf != NULL && count + 1 >= buf_len) + return count; + // fetch next character + c = fgetc(fp); + // check if this is the desired character + if(predfn(c)) { + if(write != NULL) { + *write = c; + if(count + 2 < buf_len) *(write + 1) = '\0'; + } + return count + 1; + } + // skip space characters (unless search is a space character, which is handled above) + if(isspace(c)) continue; + // write and increment + if(write != NULL) { + *write = c; + ++write; + } + ++count; + } // EOF reached + // write null terminator in place of EOF if possible + if(write != NULL) + *(write-1) = '\0'; + return count + 1; +} + +static inline +long get_key(FILE* fp, char* out, size_t out_size) { + long length = get_until(fp, out, out_size, is_colon); + if(strfirst(out, out + length, '}') == 0) + return 0; + if(feof(fp)) + return -1; + length--; + out[length] = '\0'; + return length; +} + +static inline +Variant get_value(FILE* fp, char* buffer, size_t buffer_size, int* out_end_of_object) { + long length = 0; + *out_end_of_object = 0; + do { + length += get_until(fp, buffer+length, buffer_size-length, ends_parameter); + if (length <= 0) + return UndefinedVariant(); + *out_end_of_object = strfirst(buffer, buffer + length, '}') != -1; + } while(strcount(buffer, buffer+length, '(') != strcount(buffer, buffer+length, ')') && !(*out_end_of_object)); + length--; + buffer[length] = '\0'; + return variant_from_str(buffer); +} + +static inline +int peek_next_non_blank(FILE* fp) { + long position = ftell(fp); + int c; + do { + c = fgetc(fp); + } while(isspace(c)); + fseek(fp, position, SEEK_SET); + return c; +} + +static inline +int load_args(FILE* fp, Dictionary* out, char* buffer, size_t buffer_size) { + char* key; + long length; + int is_end; + do { + length = get_key(fp, buffer, buffer_size); + if(length == -1) // detect EOF before end of key + return -1; + key = malloc(length+1); + strncpy(key, buffer, length); + Variant var = get_value(fp, buffer, buffer_size, &is_end); + dictionary_set_value(Variant, out, key, var); + free(key); + if(peek_next_non_blank(fp) == '}') { + get_until(fp, NULL, 0, ends_parameter); + break; + } + } while(!is_end); + return 0; +} + +int level_parse_file(FILE* fp) { + // initialize a buffer string + size_t length = 81; + char buffer[length]; + char* obj_tag = NULL; + Dictionary args = dictionary_new(sizeof(Variant)); + DeserializeFn spawner = NULL; + do { + // read the next line of the level file + length = get_until(fp, buffer, sizeof(buffer)-1, is_open_bracket); + --length; + buffer[length] = '\0'; + long start = strfirst(buffer, buffer+length, '}') + 1; + // initialize object tag buffer + obj_tag = malloc((length-start)+1); + obj_tag[length - start] = '\0'; + strncpy(obj_tag, buffer + start, length - start); + // find the spawn function + if(dictionary_try_get(&internal_deserializers, obj_tag, &spawner)) { + // load arguments from file and spawn object + load_args(fp, &args, buffer, sizeof(buffer)); + spawn_object(spawner, &args); + // clean up for next loop + free(obj_tag); + dictionary_empty(&args); + } else { + // clean up for next loop + free(obj_tag); + dictionary_empty(&args); + obj_tag = NULL; + // return -1; + } + } while(!feof(fp)); + return 0; +} + +int level_load_file(const char* path) { + FILE* fp = fopen("assets/test.sc", "r"); + level_parse_file(fp); + return 0; +} diff --git a/core/src/level.h b/core/src/level.h new file mode 100644 index 0000000..b8ae1fe --- /dev/null +++ b/core/src/level.h @@ -0,0 +1,18 @@ +#ifndef _fencer_level_h +#define _fencer_level_h + +#include "stdio.h" +#include "behaviour_entity.h" +#include "dictionary.h" + +typedef BehaviourEntity(*DeserializeFn)(Dictionary* args); + +extern int level_init(); +extern int level_clean(); + +extern int level_register_spawner(const char* object_tag, DeserializeFn spawn_function); + +extern int level_parse_file(FILE* level); +extern int level_load_file(const char* path); + +#endif // !_fencer_level_h diff --git a/core/src/physics.h b/core/src/physics.h new file mode 100644 index 0000000..dbc12fe --- /dev/null +++ b/core/src/physics.h @@ -0,0 +1,34 @@ +#ifndef _fencer_physics_h +#define _fencer_physics_h + +#include "vmath.h" +#include "transform.h" +#include "stdint.h" +#include "physics_entity.h" +#include "shape.h" + +typedef uint32_t PhysicsMask; + +typedef struct Collision { + struct Collider* other; + + Vector point; + Vector normal; + + Vector velocity; + Vector penetration_vector; + + Vector edge_left; + Vector edge_right; +} Collision; + +typedef struct PhysicsQuery { + Shape* shape; + Transform* transform; + PhysicsMask mask; +} PhysicsQuery; + + +#define PHYSICS_LAYER_DEFAULT 0x1 + +#endif // !_fencer_physics_h diff --git a/src/physics_entity.c b/core/src/physics_entity.c similarity index 83% rename from src/physics_entity.c rename to core/src/physics_entity.c index 5b7b45d..48f6451 100644 --- a/src/physics_entity.c +++ b/core/src/physics_entity.c @@ -4,13 +4,15 @@ #include "shape.h" #include "render.h" #include "debug.h" +#include "collider.h" void physics_entity_debug_draw(PhysicsEntity self) { RigidBody* body = self.tc->get_rigidbody(self.data); - Shape* shape = self.tc->get_shape(self.data); - Transform* transform = self.transformable->get_transform(self.data); + Transform* transform = rigidbody_get_transform(body); - shape_draw(shape, *transform); + list_foreach(Collider**, collider, rigidbody_get_colliders(body)) { + shape_draw(collider_get_shape(*collider), *transform); + } rigidbody_debug_draw_contacts(body); Vector lhs = transform->position; @@ -18,12 +20,13 @@ void physics_entity_debug_draw(PhysicsEntity self) { lhs = camera_world_to_pixel_point(&g_camera, lhs); rhs = camera_world_to_pixel_point(&g_camera, rhs); SDL_SetRenderDrawColor(g_renderer, 0, 255, 0, 255); - SDL_RenderDrawLine(g_renderer, lhs.x, lhs.y, rhs.x, rhs.y); + SDL_RenderDrawLineF(g_renderer, lhs.x, lhs.y, rhs.x, rhs.y); rhs = camera_world_to_pixel_point(&g_camera, vaddf(transform->position, rigidbody_get_force(body))); SDL_SetRenderDrawColor(g_renderer, 0, 255, 255, 255); - SDL_RenderDrawLine(g_renderer, lhs.x, lhs.y, rhs.x, rhs.y); + SDL_RenderDrawLineF(g_renderer, lhs.x, lhs.y, rhs.x, rhs.y); } + static inline Vector _internal_calculate_contact_force(RigidBody* self, Contact* contact) { const Vector velocity = contact->hit.velocity; @@ -69,7 +72,7 @@ void physics_entity_solve_contacts(PhysicsEntity self, List* contacts) { float dot = vdotf(dir, vel); if(dot < 0) - vel = vsubf(vel, vmulff(dir, dot * (1.0 + rigidbody_get_bounce(body)))); + vel = vsubf(vel, vmulff(dir, dot * (1.0f + rigidbody_get_bounce(body)))); rigidbody_set_velocity(body, vel); } @@ -81,9 +84,13 @@ void physics_entity_update(PhysicsEntity self) { List* contacts = rigidbody_get_contacts(body); if(contacts->len > 0) { - self.tc->collision_solver(self.data, contacts); - list_foreach(Contact*, contact, contacts) + if (rigidbody_is_static(body) == 0) { + physics_entity_solve_contacts(self, contacts); + } + + list_foreach(Contact *, contact, contacts) { self.tc->on_collision(self.data, contact->hit); + } } rigidbody_collect_contacts(body); diff --git a/src/physics_entity.h b/core/src/physics_entity.h similarity index 52% rename from src/physics_entity.h rename to core/src/physics_entity.h index 5fc278b..7e133c6 100644 --- a/src/physics_entity.h +++ b/core/src/physics_entity.h @@ -1,47 +1,46 @@ #ifndef _fencer_collidable_h #define _fencer_collidable_h -#include "vmath.h" #include "typeclass_helpers.h" #include "list.h" -#include "shape.h" +#include "transformable.h" +#include "mirror.h" +typedef struct Collider Collider; typedef struct Collision Collision; typedef struct RigidBody RigidBody; +typedef struct PhysicsEntity PhysicsEntity; -typedef struct { +typedef struct IPhysicsEntity { RigidBody* (*const get_rigidbody)(void* self); - Shape* (*const get_shape)(void* self); void(*const on_collision)(void* self, Collision collision); - void(*const collision_solver)(void* self, List* collisions); + void(*const on_overlap)(void* self, Collider* other); } IPhysicsEntity; -typedef struct { +typedef struct PhysicsEntity { void* data; IPhysicsEntity const* tc; ITransformable const* transformable; + IMirror const* mirror; } PhysicsEntity; extern void physics_entity_debug_draw(PhysicsEntity self); extern void physics_entity_solve_contacts(PhysicsEntity self, List* contacts); extern void physics_entity_update(PhysicsEntity self); - -#define impl_PhysicsEntity_for(T, get_rigidbody_f, get_shape_f, on_collision_f, collision_solver_f)\ -static inline PhysicsEntity T##_as_PhysicsEntity(T* x) {\ - TC_FN_TYPECHECK(Transformable, T##_as_Transformable, T*);\ +#define impl_PhysicsEntity_for(T, get_rigidbody_f, on_collision_f, on_overlap_f)\ +PhysicsEntity T##_as_PhysicsEntity(T* x) {\ TC_FN_TYPECHECK(RigidBody*, get_rigidbody_f, T*);\ - TC_FN_TYPECHECK(Shape*, get_shape_f, T*);\ TC_FN_TYPECHECK(void, on_collision_f, T*, Collision);\ - TC_FN_TYPECHECK(void, collision_solver_f, T*, List*);\ + TC_FN_TYPECHECK(void, on_overlap_f, T*, Collider*);\ static IPhysicsEntity const tc = {\ - .get_rigidbody = (RigidBody*(*const)(void*)) get_rigidbody_f,\ - .get_shape = (Shape*(*const)(void*)) get_shape_f,\ - .on_collision = (void(*const)(void*,Collision)) on_collision_f,\ - .collision_solver = (void(*const)(void*,List*)) collision_solver_f,\ + .get_rigidbody = (RigidBody*(*const)(void*)) get_rigidbody_f,\ + .on_collision = (void(*const)(void*,Collision)) on_collision_f,\ + .on_overlap = (void(*const)(void*,Collider*)) on_overlap_f,\ };\ Transformable transformable = T##_as_Transformable(x);\ - return (PhysicsEntity){.data = x, .tc = &tc, .transformable = transformable.tc};\ + Mirror mirror = T##_as_Mirror(x);\ + return (PhysicsEntity){.data = x, .tc = &tc, .transformable = transformable.tc, .mirror = mirror.tc};\ } #endif // !_fencer_collidable_h diff --git a/core/src/physics_world.c b/core/src/physics_world.c new file mode 100644 index 0000000..96c49fc --- /dev/null +++ b/core/src/physics_world.c @@ -0,0 +1,179 @@ +#include "physics_world.h" +#include "debug.h" +#include "collision.h" +#include "collider.h" +#include "rigidbody.h" + +static List _world_bodies; +static List _world_bodies_add_queue; +static List _world_bodies_remove_queue; + +void physics_world_init() { + _world_bodies = list_from_type(PhysicsEntity); + _world_bodies_add_queue = list_from_type(PhysicsEntity); + _world_bodies_remove_queue = list_from_type(PhysicsEntity); +} + +void physics_world_clean() { + list_empty(&_world_bodies); +} + +static +size_t _internal_physics_world_find_index(PhysicsEntity entity) { + for(size_t i = 0; i < _world_bodies.len; ++i) { + PhysicsEntity* found = list_at_as(PhysicsEntity, &_world_bodies, i); + if(found->data == entity.data) { + return i; + } + } + ASSERT_RETURN(0, _world_bodies.len, "Failed to find entity %p in world bodies", entity.data); +} + +static +void _internal_physics_world_add_remove() { + size_t index = 0; + list_foreach(PhysicsEntity*, entity, &_world_bodies_remove_queue) { + index = _internal_physics_world_find_index(*entity); + list_erase(&_world_bodies, index); + } + + list_empty(&_world_bodies_remove_queue); + + list_foreach(PhysicsEntity*, entity, &_world_bodies_add_queue) { + list_add(&_world_bodies, entity); + } + list_empty(&_world_bodies_add_queue); +} + +void physics_world_add_entity(PhysicsEntity entity) { + list_add(&_world_bodies_add_queue, &entity); +} + +void physics_world_remove_entity(PhysicsEntity entity) { + list_add(&_world_bodies_remove_queue, &entity); +} + +static inline +void _internal_physics_check_entities(PhysicsEntity left, PhysicsEntity right) { + RigidBody* rbleft = left.tc->get_rigidbody(left.data); + RigidBody* rbright = right.tc->get_rigidbody(right.data); + + Collision collision_left, collision_right; + int is_overlap = 0; + + list_foreach(Collider**, left_col, rigidbody_get_colliders(rbleft)) { + list_foreach(Collider**, right_col, rigidbody_get_colliders(rbright)) { + is_overlap = collider_is_overlap(*left_col) || collider_is_overlap(*right_col); + + if(collision_check(*left_col, *right_col, &collision_left, &collision_right)) { + if(is_overlap) { + left.tc->on_overlap(left.data, *right_col); + right.tc->on_overlap(right.data, *left_col); + } else { + rigidbody_add_contact(rbleft, collision_left); + rigidbody_add_contact(rbright, collision_right); + } + } + } + } +} + +static inline +void _internal_physics_narrow_collision() { + size_t half_end = _world_bodies.len/2; + + PhysicsEntity* right = NULL; + list_foreach(PhysicsEntity*, left, &_world_bodies) { + for(size_t right_index = 0; right_index <= half_end; ++right_index) { + right = list_at_as(PhysicsEntity, &_world_bodies, right_index); + + if(left->data == right->data) continue; + _internal_physics_check_entities(*left, *right); + } + } +} + +static inline +void _internal_physics_apply() { + list_foreach(PhysicsEntity*, entity, &_world_bodies) { + physics_entity_update(*entity); + } +} + +static inline +void _internal_physics_integrate_forces() { + list_foreach(PhysicsEntity*, entity, &_world_bodies) + rigidbody_integrate_forces(entity->tc->get_rigidbody(entity->data)); +} + +void physics_world_tick() { + _internal_physics_world_add_remove(); + _internal_physics_integrate_forces(); + _internal_physics_narrow_collision(); + _internal_physics_apply(); +} + +List physics_world_query_all(PhysicsQuery query, RigidBody* ignore) { + List result = list_from_type(Collider*); + list_foreach(PhysicsEntity*, entity, &_world_bodies) { + RigidBody* body = entity->tc->get_rigidbody(entity->data); + if(body == ignore) continue; + + list_foreach(Collider**, collider, rigidbody_get_colliders(body)) + if(overlap_check(query, *collider)) + list_add(&result, collider); + } + return result; +} + +List physics_world_box_query_all(Vector centre, Vector extents, PhysicsMask mask, RigidBody* ignore) { + Transform transform = { + .position = centre, + .scale = OneVector, + .rotation = 0.f + }; + Shape* shape = shape_new((Vector[]){ + MakeVector(-extents.x, -extents.y), + MakeVector(extents.x, -extents.y), + MakeVector(extents.x, extents.y), + MakeVector(-extents.x, extents.y) + }, 4); + PhysicsQuery query = { + .shape = shape, + .transform = &transform, + .mask = mask + }; + return physics_world_query_all(query, ignore); +} + +Collider* physics_world_query(PhysicsQuery query, RigidBody* ignore) { + list_foreach(PhysicsEntity*, entity, &_world_bodies) { + RigidBody* body = entity->tc->get_rigidbody(entity->data); + if(body == ignore) continue; + + list_foreach(Collider**, collider, rigidbody_get_colliders(body)) + if(overlap_check(query, *collider)) + return *collider; + } + return NULL; +} + +Collider* physics_world_box_query(Vector centre, Vector extents, PhysicsMask mask, RigidBody* ignore) { + Transform transform = { + .position = centre, + .scale = OneVector, + .rotation = 0.f + }; + Shape* shape = shape_new((Vector[]){ + MakeVector(-extents.x, -extents.y), + MakeVector(extents.x, -extents.y), + MakeVector(extents.x, extents.y), + MakeVector(-extents.x, extents.y) + }, 4); + PhysicsQuery query = { + .shape = shape, + .transform = &transform, + .mask = mask + }; + return physics_world_query(query, ignore); +} diff --git a/core/src/physics_world.h b/core/src/physics_world.h new file mode 100644 index 0000000..a249c32 --- /dev/null +++ b/core/src/physics_world.h @@ -0,0 +1,20 @@ +#ifndef _fencer_physics_world_h +#define _fencer_physics_world_h + +#include "physics_entity.h" +#include "collision.h" + +extern void physics_world_init(); +extern void physics_world_clean(); + +extern void physics_world_add_entity(PhysicsEntity entity); +extern void physics_world_remove_entity(PhysicsEntity entity); + +extern void physics_world_tick(); + +extern List physics_world_query_all(PhysicsQuery query, RigidBody* ignore); +extern List physics_world_box_query_all(Vector centre, Vector extents, PhysicsMask mask, RigidBody* ignore); +extern Collider* physics_world_query(PhysicsQuery query, RigidBody* ignore); +extern Collider* physics_world_box_query(Vector centre, Vector extents, PhysicsMask mask, RigidBody* ignore); + +#endif // !_fencer_physics_world_h diff --git a/src/player_input.c b/core/src/player_input.c similarity index 88% rename from src/player_input.c rename to core/src/player_input.c index d8220f8..eca8519 100644 --- a/src/player_input.c +++ b/core/src/player_input.c @@ -1,7 +1,13 @@ #include "player_input.h" +#include "debug.h" + +impl_Drop_for(PlayerInput, + playerinput_drop +) PlayerInput* playerinput_new(void* target, int device) { PlayerInput* self = malloc(sizeof(PlayerInput)); + ASSERT_RETURN(self != NULL, NULL, "Could not allocate memory for PlayerInput instance"); self->listeners = list_from_type(InputListener); self->device = input_get_device_by_id(device); self->target = target; diff --git a/src/player_input.h b/core/src/player_input.h similarity index 88% rename from src/player_input.h rename to core/src/player_input.h index acadf79..18bced8 100644 --- a/src/player_input.h +++ b/core/src/player_input.h @@ -4,6 +4,7 @@ #include "list.h" #include "input.h" #include "input_axis.h" +#include "typeclass_helpers.h" typedef struct PlayerInput { InputDevice* device; @@ -16,8 +17,6 @@ extern void playerinput_add(PlayerInput* self, InputAxis axis, InputDelegateFn d extern void playerinput_set_device(PlayerInput* self, int device); extern void playerinput_drop(PlayerInput* self); -impl_Drop_for(PlayerInput, - playerinput_drop -) +decl_typeclass_impl(Drop, PlayerInput) #endif // !_fencer_player_input_h diff --git a/src/program.c b/core/src/program.c similarity index 81% rename from src/program.c rename to core/src/program.c index 62a7220..fee1a71 100644 --- a/src/program.c +++ b/core/src/program.c @@ -2,6 +2,7 @@ #include "camera.h" #include "game_world.h" #include "physics_world.h" +#include "level.h" #include "time.h" #include "assets.h" #include "debug.h" @@ -25,7 +26,7 @@ double tstos(struct timespec ts) { struct timespec get_time() { struct timespec ts; - timespec_get(&ts, TIME_UTC); + (void)timespec_get(&ts, TIME_UTC); return ts; } @@ -33,6 +34,13 @@ double get_time_s() { return tstos(get_time()); } +static +void program_tick(const struct ProgramSettings* settings, float delta_time) { + settings->on_tick(); + game_world_update(); + physics_world_tick(); +} + void program_run(const struct ProgramSettings* settings) { LOG_INFO("Starting program..."); if(settings->target_fps <= 0) { @@ -60,6 +68,7 @@ void program_run(const struct ProgramSettings* settings) { assets_init(); input_init(); game_world_init(); + level_init(); LOG_INFO("settings->on_play"); settings->on_play(); @@ -73,13 +82,17 @@ void program_run(const struct ProgramSettings* settings) { _frame_start = current_time; program_handle_events(); - while(_delta_time > _target_delta_time) { - _delta_time -= _target_delta_time; - settings->on_tick(); - game_world_update(); - physics_world_tick(); + if(settings->target_fps == 0) { + program_tick(settings, _delta_time); + _delta_time = 0.f; + } else { + while(_delta_time > _target_delta_time) { + _delta_time -= _target_delta_time; + program_tick(settings, _target_delta_time); + } } settings->on_draw(); + game_world_draw(); SDL_Delay(1); } @@ -88,6 +101,7 @@ void program_run(const struct ProgramSettings* settings) { } void program_quit() { + level_clean(); game_world_close(); input_clean(); assets_clean(); @@ -132,10 +146,10 @@ void program_handle_windowevent(SDL_WindowEvent* event) { inline float delta_time() { - return _target_delta_time == 0 ? _delta_time : _target_delta_time; + return (float)(_target_delta_time == 0 ? _delta_time : _target_delta_time); } inline float game_time() { - return get_time_s() - _game_start_time; + return (float)(get_time_s() - _game_start_time); } diff --git a/src/program.h b/core/src/program.h similarity index 100% rename from src/program.h rename to core/src/program.h diff --git a/src/render.c b/core/src/render.c similarity index 95% rename from src/render.c rename to core/src/render.c index 57e11ad..f569593 100644 --- a/src/render.c +++ b/core/src/render.c @@ -62,10 +62,10 @@ void render_calculate_render_area() { // calculate the largest area that will fit the entire rendertexture into the window space g_render_area = (SDL_Rect) {0, 0, window_resolution.x, window_resolution.y}; if(window_aspect <= target_aspect) { - g_render_area.h = window_resolution.x / target_aspect; + g_render_area.h = (int)((float)window_resolution.x / target_aspect); g_render_area.y = (window_resolution.y - g_render_area.h) / 2; } else { - g_render_area.w = window_resolution.y * target_aspect; + g_render_area.w = (int)((float)window_resolution.y * target_aspect); g_render_area.x += (window_resolution.x - g_render_area.w) / 2; } } diff --git a/src/render.h b/core/src/render.h similarity index 100% rename from src/render.h rename to core/src/render.h diff --git a/src/rigidbody.c b/core/src/rigidbody.c similarity index 69% rename from src/rigidbody.c rename to core/src/rigidbody.c index 09b1c87..91d8ee3 100644 --- a/src/rigidbody.c +++ b/core/src/rigidbody.c @@ -6,10 +6,9 @@ #include "transformable.h" struct RigidBody { - Transformable transformable; + PhysicsEntity owner; float mass; - float bounce; Vector last_linear_force; @@ -17,16 +16,25 @@ struct RigidBody { Vector linear_velocity; Transform internal_transform; + PhysicsMask layers; + PhysicsMask collision_mask; + + List colliders; + int is_static; List contacts; }; -RigidBody* rigidbody_make(Transformable transform) { +impl_Transformable_for(RigidBody, + rigidbody_get_transform +) + +RigidBody* rigidbody_make(PhysicsEntity owner) { RigidBody* self = malloc(sizeof(RigidBody)); ASSERT_RETURN(self != NULL, NULL, "Failed to allocate space for rigidbody"); *self = (RigidBody){ - .transformable = transform, + .owner = owner, .mass = 1.0f, .bounce = 0.0f, @@ -34,11 +42,18 @@ RigidBody* rigidbody_make(Transformable transform) { .next_linear_force = ZeroVector, .last_linear_force = ZeroVector, - .internal_transform = *transform.tc->get_transform(transform.data), + .internal_transform = *owner.transformable->get_transform(owner.data), + .layers = 0x1, + .collision_mask = 0x1, + + .colliders = list_from_type(Collider*), + .is_static = 0, + .contacts = list_from_type(Contact), }; + self->internal_transform.scale = OneVector; return self; } @@ -73,15 +88,18 @@ void _internal_debug_draw_collision_edge(RigidBody* self, Contact* contact) { Vector b = camera_world_to_pixel_point(&g_camera, right); Vector n = transform_direction(&g_camera.transform, contact->hit.normal); SDL_SetRenderDrawColor(g_renderer, 255, 2, 255, 255); - SDL_RenderDrawLine(g_renderer, a.x, a.y, b.x, b.y); + SDL_RenderDrawLineF(g_renderer, a.x, a.y, b.x, b.y); a = camera_world_to_pixel_point(&g_camera, point); b = vaddf(a, vmulff(n, 100.f)); SDL_SetRenderDrawColor(g_renderer, 255, 0, 0, 255); - SDL_RenderDrawLine(g_renderer, a.x, a.y, b.x, b.y); + SDL_RenderDrawLineF(g_renderer, a.x, a.y, b.x, b.y); #endif } void rigidbody_integrate_forces(RigidBody* self) { + if(self->is_static) + return; + const float dt = delta_time(); Vector position = self->internal_transform.position; @@ -96,7 +114,8 @@ void rigidbody_integrate_forces(RigidBody* self) { self->linear_velocity = velocity; self->internal_transform.position = position; - transformable_set_position(self->transformable, position); + Transform* owner_trans = self->owner.transformable->get_transform(self->owner.data); + owner_trans->position = position; self->last_linear_force = self->next_linear_force; self->next_linear_force = ZeroVector; @@ -129,12 +148,28 @@ void rigidbody_accelerate(RigidBody* self, Vector force, int use_mass) { self->next_linear_force = vaddf(self->next_linear_force, force); } -int rigidbody_is_static(const RigidBody* self) { +PhysicsMask rigidbody_get_layers(RigidBody* self) { + return self->layers; +} + +void rigidbody_set_layers(RigidBody* self, PhysicsMask layers) { + self->layers = layers; +} + +PhysicsMask rigidbody_get_collision_mask(RigidBody* self) { + return self->collision_mask; +} + +void rigidbody_set_collision_mask(RigidBody* self, PhysicsMask mask) { + self->collision_mask = mask; +} + +int rigidbody_is_static(RigidBody* self) { return self->is_static; } -void rigidbody_set_static(RigidBody* self, int is_static) { - self->is_static = is_static; +void rigidbody_set_static(RigidBody* self, int value) { + self->is_static = value != 0; } Vector rigidbody_get_velocity(const RigidBody* self) { @@ -150,6 +185,23 @@ Vector rigidbody_get_force(RigidBody* self) { return self->next_linear_force; } +void rigidbody_add_collider(RigidBody *self, Collider* collider) { + list_add(&self->colliders, &collider); +} + +void rigidbody_remove_collider(RigidBody *self, Collider* collider) { + for(size_t i = 0; i < self->colliders.len; ++i) { + if(collider == *list_at_as(Collider*, &self->colliders, i)) { + list_erase(&self->colliders, i); + return; + } + } +} + +List* rigidbody_get_colliders(RigidBody* self) { + return &self->colliders; +} + void rigidbody_debug_draw_contacts(RigidBody* self) { list_foreach(Contact*, contact, &self->contacts) { _internal_debug_draw_collision_edge(self, contact); diff --git a/src/rigidbody.h b/core/src/rigidbody.h similarity index 60% rename from src/rigidbody.h rename to core/src/rigidbody.h index 74e5ba5..e34b7b0 100644 --- a/src/rigidbody.h +++ b/core/src/rigidbody.h @@ -1,21 +1,22 @@ #ifndef _fencer_rigidbody_h #define _fencer_rigidbody_h -#include "shape.h" #include "transformable.h" #include "list.h" -#include "collision.h" +#include "physics.h" -struct Collision; typedef struct { - struct Collision hit; + Collision hit; float duration; } Contact; + typedef struct RigidBody RigidBody; +typedef struct PhysicsEntity PhysicsEntity; + typedef void (*CollisionHandlerFn)(void* obj, List* collisions); // Referenced transform is stored but not owned by the rigidbody. -extern RigidBody* rigidbody_make(Transformable transform); +extern RigidBody* rigidbody_make(PhysicsEntity owner); extern void rigidbody_destroy(RigidBody* self); extern void rigidbody_add_contact(RigidBody* self, struct Collision hit); @@ -33,19 +34,31 @@ extern void rigidbody_set_bounce(RigidBody* self, float bounce); extern void rigidbody_add_impulse(RigidBody* self, Vector force, int use_mass); extern void rigidbody_accelerate(RigidBody* self, Vector force, int use_mass); -extern int rigidbody_is_static(const RigidBody* self); -extern void rigidbody_set_static(RigidBody* self, int is_static); +extern PhysicsMask rigidbody_get_layers(RigidBody* self); +extern void rigidbody_set_layers(RigidBody* self, PhysicsMask layers); + +extern PhysicsMask rigidbody_get_collision_mask(RigidBody* self); +extern void rigidbody_set_collision_mask(RigidBody* self, PhysicsMask mask); + +extern int rigidbody_get_overlap(RigidBody* self); +extern void rigidbody_set_overlap(RigidBody* self, int value); + +extern int rigidbody_is_static(RigidBody* self); +extern void rigidbody_set_static(RigidBody* self, int value); extern Vector rigidbody_get_velocity(const RigidBody* self); extern void rigidbody_set_velocity(RigidBody* self, Vector velocity); extern Vector rigidbody_get_force(RigidBody* self); +extern void rigidbody_add_collider(RigidBody* self, Collider* collider); +extern void rigidbody_remove_collider(RigidBody* self, Collider* collider); +extern List* rigidbody_get_colliders(RigidBody* self); + extern void rigidbody_debug_draw_contacts(RigidBody* self); + extern Transform* rigidbody_get_transform(RigidBody* self); -impl_Transformable_for(RigidBody, - rigidbody_get_transform -) +decl_typeclass_impl(Transformable, RigidBody) #endif // !_fencer_rigidbody_h diff --git a/src/shape.c b/core/src/shape.c similarity index 81% rename from src/shape.c rename to core/src/shape.c index f566571..7dee5f2 100644 --- a/src/shape.c +++ b/core/src/shape.c @@ -11,6 +11,7 @@ struct Shape { Vector mean; int is_convex; + Vector min, max; }; static @@ -40,7 +41,7 @@ int _shape_calculate_is_convex(Shape* self) { // point relative to mean Vector relative; list_foreach(Vector*, point, &self->points) { - relative = vsubf(*point, self->mean); + relative = vnormalizedf(vsubf(*point, self->mean)); if(point != _shape_get_furthest_in_direction(self, relative)) { return 0; } @@ -55,7 +56,7 @@ Vector _shape_calculate_mean(Shape* self) { size_t count = 0; list_foreach(Vector*, point, &self->points) { ++count; - avg = vaddf(avg, vmulff(*point, 1.0/count)); + avg = vaddf(avg, vmulff(*point, 1.f/count)); } return avg; @@ -86,10 +87,10 @@ Shape* shape_new(const Vector* points, size_t points_len) { Shape* shape_new_square(Vector size) { return shape_new((Vector[4]){ - ZeroVector, - (Vector){size.x, 0.f}, - size, - (Vector){0.f, size.y}, + MakeVector(-size.x, -size.y), + MakeVector(size.x, -size.y), + MakeVector(size.x, size.y), + MakeVector(-size.x, size.y) }, 4); } @@ -183,3 +184,29 @@ void shape_draw(Shape* self, Transform transform) { SDL_RenderDrawLineF(g_renderer, lhs.x, lhs.y, rhs.x, rhs.y); } } + +Vector shape_get_min_extent(Shape* self, Transform* transform) { + if(self->points.len == 0) + return ZeroVector; + Vector min = *list_at_as(Vector, &self->points, 0); + Vector point; + for(size_t i = 0; i < shape_get_points_count(self); ++i) { + point = shape_get_point_transformed(self, i, *transform); + min.x = fminf(min.x, point.x); + min.y = fminf(min.y, point.y); + } + return min; +} + +Vector shape_get_max_extent(Shape* self, Transform* transform) { + if(self->points.len == 0) + return ZeroVector; + Vector max = *list_at_as(Vector, &self->points, 0); + Vector point; + for(size_t i = 0; i < shape_get_points_count(self); ++i) { + point = shape_get_point_transformed(self, i, *transform); + max.x = fmaxf(max.x, point.x); + max.y = fmaxf(max.y, point.y); + } + return max; +} diff --git a/src/shape.h b/core/src/shape.h similarity index 88% rename from src/shape.h rename to core/src/shape.h index 650ab82..ee4bf9c 100644 --- a/src/shape.h +++ b/core/src/shape.h @@ -29,4 +29,7 @@ extern Vector shape_get_median_point(Shape* self); extern int shape_is_convex(Shape* self); extern void shape_draw(Shape* self, Transform transform); +extern Vector shape_get_min_extent(Shape* self, Transform* transform); +extern Vector shape_get_max_extent(Shape* self, Transform* transform); + #endif // !_fencer_shape_h diff --git a/src/sprite.c b/core/src/sprite.c similarity index 85% rename from src/sprite.c rename to core/src/sprite.c index ac86bd8..a0d5414 100644 --- a/src/sprite.c +++ b/core/src/sprite.c @@ -19,16 +19,23 @@ struct Sprite { SDL_RendererFlip flip_state; }; -Sprite* sprite_from_spritesheet(Spritesheet* sheet, size_t initial_frame) { +Sprite* sprite_new_empty() { Sprite* self = malloc(sizeof(Sprite)); - ASSERT_RETURN(self != NULL, NULL, "Failed to allocate memory for new sprite."); + *self = (Sprite){ + .spritesheet = NULL, + .tile_index = 0, + .origin = ZeroVector, + .flip_state = SDL_FLIP_NONE, + }; + return self; +} +Sprite* sprite_from_spritesheet(Spritesheet* sheet, size_t initial_frame) { + Sprite* self = sprite_new_empty(); self->spritesheet = sheet; - self->origin = (Vector){0.5f, 0.5f}; + self->origin = VectorFrom(0.5f); self->tile_index = initial_frame; - self->flip_state = SDL_FLIP_NONE; - return self; } @@ -42,10 +49,10 @@ void sprite_draw(Sprite* self, Transform transform) { Vector origin = self->origin; if(self->flip_state && SDL_FLIP_HORIZONTAL) { - origin.x = 1.0-origin.x; + origin.x = 1.0f-origin.x; } if((self->flip_state & SDL_FLIP_VERTICAL) != 0) { - origin.y = 1.0-origin.y; + origin.y = 1.0f-origin.y; } Vector left_top = transform_point(&transform, vinvf(origin)); @@ -75,8 +82,7 @@ size_t sprite_get_tile(const Sprite* self) { } void sprite_set_tile(Sprite* self, size_t frame) { - frame = frame % spritesheet_get_tile_count(self->spritesheet); - self->tile_index = frame; + self->tile_index = frame % spritesheet_get_tile_count(self->spritesheet); } Spritesheet* sprite_get_spritesheet(const Sprite* self) { diff --git a/src/sprite.h b/core/src/sprite.h similarity index 96% rename from src/sprite.h rename to core/src/sprite.h index 8fdf0ac..b75279c 100644 --- a/src/sprite.h +++ b/core/src/sprite.h @@ -10,6 +10,7 @@ // Forward declaration of the private sprite struct typedef struct Sprite Sprite; +extern Sprite* sprite_new_empty(); extern Sprite* sprite_from_spritesheet(Spritesheet* sheet, size_t initial_frame); extern void sprite_destroy(Sprite* sprite); diff --git a/src/sprite_entity.c b/core/src/sprite_entity.c similarity index 100% rename from src/sprite_entity.c rename to core/src/sprite_entity.c diff --git a/src/sprite_entity.h b/core/src/sprite_entity.h similarity index 100% rename from src/sprite_entity.h rename to core/src/sprite_entity.h diff --git a/src/spritesheet.c b/core/src/spritesheet.c similarity index 83% rename from src/spritesheet.c rename to core/src/spritesheet.c index 777492a..b6cf4be 100644 --- a/src/spritesheet.c +++ b/core/src/spritesheet.c @@ -20,6 +20,15 @@ struct Spritesheet { IVector tile_size; }; +impl_Drop_for(Spritesheet, + _internal_spritesheet_destroy +) + +impl_Asset_for(Spritesheet, + spritesheet_get_asset_id, + spritesheet_set_asset_id +) + void _internal_spritesheet_destroy(Spritesheet* self) { SDL_DestroyTexture(self->texture); free(self); @@ -43,8 +52,8 @@ Spritesheet* spritesheet_from_texture(SDL_Texture* texture, IVector tile_size) { SDL_QueryTexture(self->texture, NULL, NULL, &self->resolution.x, &self->resolution.y); self->tile_size = tile_size; - self->tile_shear = self->resolution.x / self->tile_size.x; - self->tile_count = self->resolution.x / self->tile_size.x * self->resolution.y / self->tile_size.y; + self->tile_shear = (size_t)self->resolution.x / self->tile_size.x; + self->tile_count = (size_t)self->resolution.x / self->tile_size.x * self->resolution.y / self->tile_size.y; return self; } @@ -57,7 +66,7 @@ SDL_Texture* spritesheet_get_texture(const Spritesheet* self) { } SDL_Rect spritesheet_get_tile_rect(const Spritesheet* self, size_t index) { - IVector tile_coord = {index % self->tile_shear, index / self->tile_shear}; + IVector tile_coord = {(int)(index % self->tile_shear), (int)(index / self->tile_shear)}; tile_coord = vmuli(tile_coord, self->tile_size); return (SDL_Rect) { diff --git a/src/spritesheet.h b/core/src/spritesheet.h similarity index 85% rename from src/spritesheet.h rename to core/src/spritesheet.h index 8d76e38..71e18cd 100644 --- a/src/spritesheet.h +++ b/core/src/spritesheet.h @@ -2,6 +2,7 @@ #define _fencer_spritesheet_h #include "asset.h" +#include "typeclass_helpers.h" #include "vmath.h" #include @@ -21,13 +22,7 @@ extern void spritesheet_set_asset_id(Spritesheet* self, asset_id id); extern void _internal_spritesheet_destroy(Spritesheet* self_void); -impl_Drop_for(Spritesheet, - _internal_spritesheet_destroy -) - -impl_Asset_for(Spritesheet, - spritesheet_get_asset_id, - spritesheet_set_asset_id -) +decl_typeclass_impl(Drop, Spritesheet) +decl_typeclass_impl(Asset, Spritesheet) #endif // !_fencer_spritesheet_h diff --git a/core/src/state.h b/core/src/state.h new file mode 100644 index 0000000..14a6645 --- /dev/null +++ b/core/src/state.h @@ -0,0 +1,27 @@ +#ifndef _fencer_state_h +#define _fencer_state_h + +#include "typeclass_helpers.h" + +typedef struct State State; + +struct State { + void (*const enter)(void* data); + void (*const exit)(void* data); + const State* (*const update)(void* data, float dt); +}; + +#define DefineState(_StateName, _DataType, enter_fn, update_fn, exit_fn)\ +static inline const State* _StateName() {\ + TC_FN_TYPECHECK(void, enter_fn, _DataType*);\ + TC_FN_TYPECHECK(const State*, update_fn, _DataType*, float);\ + TC_FN_TYPECHECK(void, exit_fn, _DataType*);\ + static const State instance = {\ + .enter = (void(*const)(void*)) enter_fn,\ + .update = (const State*(*const)(void*, float)) update_fn,\ + .exit = (void(*const)(void*)) exit_fn,\ + };\ + return &instance;\ +} + +#endif // !_fencer_state_h diff --git a/core/src/state_machine.c b/core/src/state_machine.c new file mode 100644 index 0000000..7948681 --- /dev/null +++ b/core/src/state_machine.c @@ -0,0 +1,41 @@ +#include "state_machine.h" +#include "stdlib.h" +#include "debug.h" + +struct StateMachine { + const State* current_state; + void* data; +}; + +static inline +void internal_state_machine_set_state(StateMachine* self, const State* state) { + self->current_state->exit(self->data); + self->current_state = state; + self->current_state->enter(self->data); +} + +StateMachine* state_machine_init(void* data, const State* start_state) { + StateMachine* self = malloc(sizeof(StateMachine)); + ASSERT_RETURN(self != NULL, NULL, "Failed to allocate space for StateMachine instance"); + *self = (StateMachine){ + .current_state = start_state, + .data = data + }; + + self->current_state->enter(self->data); + return self; +} + +void state_machine_destroy(StateMachine* self) { + free(self); +} + +void state_machine_update(StateMachine* self, float dt) { + const State* next = self->current_state->update(self->data, dt); + if(next != self->current_state) + internal_state_machine_set_state(self, next); +} + +const State* state_machine_get_current_state(StateMachine* self) { + return self->current_state; +} diff --git a/core/src/state_machine.h b/core/src/state_machine.h new file mode 100644 index 0000000..0d5c2fb --- /dev/null +++ b/core/src/state_machine.h @@ -0,0 +1,15 @@ +#ifndef _fencer_state_machine_h +#define _fencer_state_machine_h + +#include "state.h" + +typedef struct StateMachine StateMachine; + +extern StateMachine* state_machine_init(void* data, const State* start_state); +extern void state_machine_destroy(StateMachine* self); + +extern void state_machine_update(StateMachine* self, float dt); + +extern const State* state_machine_get_current_state(StateMachine* self); + +#endif // !_fencer_state_machine_h diff --git a/core/src/transform.c b/core/src/transform.c new file mode 100644 index 0000000..edef40c --- /dev/null +++ b/core/src/transform.c @@ -0,0 +1,5 @@ +#include "transform.h" + +impl_Transformable_for(Transform, + transform_get_transform +) diff --git a/src/transform.h b/core/src/transform.h similarity index 95% rename from src/transform.h rename to core/src/transform.h index 7ed243e..4c657de 100644 --- a/src/transform.h +++ b/core/src/transform.h @@ -1,6 +1,7 @@ #ifndef _fencer_transform_h #define _fencer_transform_h +#include "typeclass_helpers.h" #include "vmath.h" #include "transformable.h" @@ -56,8 +57,6 @@ Transform* transform_get_transform(Transform* self) { return self; } -impl_Transformable_for(Transform, - transform_get_transform -); +decl_typeclass_impl(Transformable, Transform) #endif // !_fencer_transform_h diff --git a/src/transformable.c b/core/src/transformable.c similarity index 100% rename from src/transformable.c rename to core/src/transformable.c diff --git a/src/transformable.h b/core/src/transformable.h similarity index 95% rename from src/transformable.h rename to core/src/transformable.h index 0ac7531..1e38d61 100644 --- a/src/transformable.h +++ b/core/src/transformable.h @@ -26,7 +26,7 @@ extern void transformable_set_rotation(Transformable self, float rotation); extern void transformable_rotate(Transformable self, float delta); #define impl_Transformable_for(T, get_transform_f)\ -static inline Transformable T##_as_Transformable(T* x) {\ +Transformable T##_as_Transformable(T* x) {\ TC_FN_TYPECHECK(Transform*, get_transform_f, T*);\ static ITransformable const tc = {\ .get_transform = (Transform*(*const)(void*)) get_transform_f\ diff --git a/core/src/utils b/core/src/utils new file mode 160000 index 0000000..f84e3a3 --- /dev/null +++ b/core/src/utils @@ -0,0 +1 @@ +Subproject commit f84e3a3a27d626405173bd84055c299969cf7033 diff --git a/design/Core.md b/design/Core.md deleted file mode 100644 index 01cc239..0000000 --- a/design/Core.md +++ /dev/null @@ -1,26 +0,0 @@ -# Mechanics - -## Movement - -Slow and considered. -The walking speed is slow, the running speed slightly less so. -Walkable parts of environments are relatively small. - -## Combat - - - -## Exploration - -Resources are scattered throughout the environment. -It should be rewarding to stop and gather them. - -## Upgrades - - - -# Dynamics - -## Careful Progress - -The player should be careful and considered during second to second gameplay. diff --git a/game/Build-Game.lua b/game/Build-Game.lua new file mode 100644 index 0000000..1d5fb56 --- /dev/null +++ b/game/Build-Game.lua @@ -0,0 +1,52 @@ +project "Game" + kind "WindowedApp" + language "C" + staticruntime "Off" + targetdir "bin/%{cfg.buildcfg}" + debugdir "." + + defines { "VMATH_SDL" } + + files { "src/**.c", "src/**.h" } + includedirs { + "src/", + "../core/src/", + "../core/src/utils" + } + links { + "Engine-Core" + } + + buildinputs { "assets/**" } + + postbuildcommands { + "{RMDIR} %{cfg.targetdir}/assets/", + "{COPYDIR} assets/ %{cfg.targetdir}/assets/" + } + + targetdir ("../bin/" .. OutputDir .. "/%{prj.name}" ) + objdir ("../intermediate/" .. OutputDir .. "/%{prj.name}" ) + + filter "system:linux" + links { "SDL2", "SDL2_image", "SDL2_ttf", "m", "cjson"} + + filter "system:windows" + linkoptions { "/ENTRY:mainCRTStartup" } + links { "../SDL2.dll", "../SDL2_image.dll" } + + filter "configurations:Debug" + defines { "DEBUG" } + runtime "Debug" + symbols "On" + + filter "configurations:Release" + defines { "RELEASE" } + runtime "Release" + optimize "On" + symbols "On" + + filter "configurations:Dist" + defines { "DIST" } + runtime "Release" + optimize "On" + symbols "Off" diff --git a/game/asset-src/bag.svg b/game/asset-src/bag.svg new file mode 100644 index 0000000..382683e --- /dev/null +++ b/game/asset-src/bag.svg @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/game/asset-src/player.svg b/game/asset-src/player.svg new file mode 100644 index 0000000..aac2113 --- /dev/null +++ b/game/asset-src/player.svg @@ -0,0 +1,1408 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/game/asset-src/player_v2_sketches.svg b/game/asset-src/player_v2_sketches.svg new file mode 100644 index 0000000..3609725 --- /dev/null +++ b/game/asset-src/player_v2_sketches.svg @@ -0,0 +1,1508 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/game/assets/Player_Air_Heavy.png b/game/assets/Player_Air_Heavy.png new file mode 100644 index 0000000..ebf5f0c Binary files /dev/null and b/game/assets/Player_Air_Heavy.png differ diff --git a/game/assets/Player_Hurt.png b/game/assets/Player_Hurt.png new file mode 100644 index 0000000..d3f8786 Binary files /dev/null and b/game/assets/Player_Hurt.png differ diff --git a/game/assets/Player_Idle.png b/game/assets/Player_Idle.png new file mode 100644 index 0000000..9f41863 Binary files /dev/null and b/game/assets/Player_Idle.png differ diff --git a/game/assets/Player_Jab_A.png b/game/assets/Player_Jab_A.png new file mode 100644 index 0000000..370be90 Binary files /dev/null and b/game/assets/Player_Jab_A.png differ diff --git a/game/assets/Player_Jab_B.png b/game/assets/Player_Jab_B.png new file mode 100644 index 0000000..45859e9 Binary files /dev/null and b/game/assets/Player_Jab_B.png differ diff --git a/game/assets/Player_Jumping.png b/game/assets/Player_Jumping.png new file mode 100644 index 0000000..0616bea Binary files /dev/null and b/game/assets/Player_Jumping.png differ diff --git a/game/assets/Player_Kick_A.png b/game/assets/Player_Kick_A.png new file mode 100644 index 0000000..c7be50f Binary files /dev/null and b/game/assets/Player_Kick_A.png differ diff --git a/game/assets/Player_Slash.png b/game/assets/Player_Slash.png new file mode 100644 index 0000000..8e6d756 Binary files /dev/null and b/game/assets/Player_Slash.png differ diff --git a/game/assets/Player_Slide.png b/game/assets/Player_Slide.png new file mode 100644 index 0000000..74f658a Binary files /dev/null and b/game/assets/Player_Slide.png differ diff --git a/game/assets/Player_Walk.png b/game/assets/Player_Walk.png new file mode 100644 index 0000000..3b49902 Binary files /dev/null and b/game/assets/Player_Walk.png differ diff --git a/game/assets/bag.png b/game/assets/bag.png new file mode 100644 index 0000000..bd39944 Binary files /dev/null and b/game/assets/bag.png differ diff --git a/game/assets/test.sc b/game/assets/test.sc new file mode 100644 index 0000000..8c160fc --- /dev/null +++ b/game/assets/test.sc @@ -0,0 +1,7 @@ +Player { + position: Vector(0, 0), +} + +Enemy { + position: Vector(2, 0) +} diff --git a/src/fencer.c b/game/src/BeatEmUp.c similarity index 60% rename from src/fencer.c rename to game/src/BeatEmUp.c index 47ab192..4bc2d39 100644 --- a/src/fencer.c +++ b/game/src/BeatEmUp.c @@ -1,9 +1,13 @@ -#include "camera.h" +#include "Player.h" +#include "Enemy.h" +#include "level.h" #include "program.h" static void play() { - g_camera.fov = 40; + level_register_spawner("Player", SpawnPlayer); + level_register_spawner("Enemy", SpawnEnemy); + level_load_file("assets/test.sc"); } static @@ -14,7 +18,7 @@ void draw() {} int main(int argc, char* argv[]) { struct ProgramSettings config = { - .target_fps = 240, + .target_fps = 0, .title = "fencer", .view_resolution = {1920, 1080}, .on_play = &play, diff --git a/game/src/Damagable.h b/game/src/Damagable.h new file mode 100644 index 0000000..5206579 --- /dev/null +++ b/game/src/Damagable.h @@ -0,0 +1,34 @@ +#ifndef FIGHT_DAMAGABLE_H +#define FIGHT_DAMAGABLE_H + +#include "typeclass_helpers.h" + +#include "vmath.h" + +typedef struct DamageEventData { + int damageAmount; + int knockdown; + float stun; + float knockback; + Vector origin; +} DamageEventData; + +typedef struct { + int (*const damage)(void*, DamageEventData*); +} IDamagable; + +typedef struct { + void* data; + IDamagable const* tc; +} Damagable; + +#define impl_Damagable_for(T, damage_f)\ +Damagable T##_as_Damagable(T* x) {\ + TC_FN_TYPECHECK(int, damage_f, T*, DamageEventData*);\ + static const IDamagable tc = {\ + .damage = (int(*const)(void*, DamageEventData*)) damage_f,\ + };\ + return (Damagable){.data = x, .tc = &tc};\ +} + +#endif // !FIGHT_DAMAGABLE_H diff --git a/game/src/Enemy.c b/game/src/Enemy.c new file mode 100644 index 0000000..d4ed25a --- /dev/null +++ b/game/src/Enemy.c @@ -0,0 +1,156 @@ +#include "Enemy.h" +#include "Layers.h" +#include "debug.h" +#include "game_world.h" +#include "physics.h" +#include "physics_world.h" +#include "program.h" +#include "sprite.h" +#include "variant.h" + +START_REFLECT(Enemy); + REFLECT_TYPECLASS(Enemy, Transformable); + REFLECT_TYPECLASS(Enemy, Drop); + REFLECT_TYPECLASS(Enemy, PhysicsEntity); + REFLECT_TYPECLASS(Enemy, BehaviourEntity); + REFLECT_TYPECLASS(Enemy, Damagable); +END_REFLECT(Enemy); + +impl_Transformable_for(Enemy, + EnemyGetTransform +) + +impl_Drop_for(Enemy, + EnemyDestroy +) + +impl_BehaviourEntity_for(Enemy, + EnemyStart, + EnemyUpdate, + EnemyDraw, + EnemyGetDepth +) + +impl_PhysicsEntity_for(Enemy, + EnemyGetRigidBody, + EnemyOnCollision, + EnemyOnOverlap +) + +impl_Damagable_for(Enemy, + EnemyDamage +) + +Enemy* MakeEnemy() { + Enemy* self = malloc(sizeof(Enemy)); + ASSERT_RETURN(self != NULL, NULL, "Failed to allocate Enemy"); + *self = (Enemy){ + .transform = IdentityTransform, + .behaviour = NULL, + .rigidbody = NULL, + .collider = NULL, + .hitbox = NULL, + .sprite = sprite_new_empty(), + .facing = 1, + .stun_time = 0.f, + .idleAnim = NULL, + .walkAnim = NULL, + .hurtAnim = NULL, + .currentAnimation = NULL, + .health = 15, + }; + + self->rigidbody = rigidbody_make(Enemy_as_PhysicsEntity(self)); + // collider used for physics and movement + self->collider = collider_new(Enemy_as_PhysicsEntity(self), shape_new_square(MakeVector(0.2f, 0.05f)), 0, + PHYSICS_LAYER_DEFAULT, PHYSICS_LAYER_DEFAULT); + // hitbox used to detect damage + self->hitbox = collider_new(Enemy_as_PhysicsEntity(self), shape_new((Vector[]){ + MakeVector(-0.1f, -0.9f), + MakeVector( 0.1f, -0.9f), + MakeVector( 0.1f, 0.0f), + MakeVector(-0.1f, 0.0f), + }, 4), 1, PHYSICS_LAYER_COMBAT, 0x0); // does not query + + sprite_set_origin(self->sprite, MakeVector(0.45f, 0.925f)); + + // load and configure animations + self->idleAnim = animation_sprite_new(self->sprite, spritesheet_load("assets/Player_Idle.png", IVectorFrom(512)), 1.5f, LoopMode_Loop, NULL, 0); + self->walkAnim = animation_sprite_new(self->sprite, spritesheet_load("assets/Player_Walk.png", IVectorFrom(512)), 1.5f, LoopMode_Loop, NULL, 0); + self->hurtAnim = animation_sprite_new(self->sprite, spritesheet_load("assets/Player_Hurt.png", IVectorFrom(512)), 5.f, LoopMode_Stop, NULL, 0); + self->behaviour = state_machine_init(self, EnemyIdle()); + + return self; +} + +BehaviourEntity SpawnEnemy(Dictionary* args) { + Variant value; + Enemy* self = MakeEnemy(); + // spawn at location + if(dictionary_try_get(args, "position", &value) && value.type == Variant_Vector) + rigidbody_get_transform(self->rigidbody)->position = value.as_vector; + return Enemy_as_BehaviourEntity(self); +} + +void EnemyStart(Enemy* self) {} + +void EnemyUpdate(Enemy* self, float deltaTime) { + // apply drag + Vector velocity = vmovetowardsf(rigidbody_get_velocity(self->rigidbody), ZeroVector, 80.0f * deltaTime); + rigidbody_set_velocity(self->rigidbody, velocity); + // update state machine + state_machine_update(self->behaviour, deltaTime); +} + +void EnemyDraw(Enemy* self) { + sprite_flip_horizontal(self->sprite, self->facing == -1); + animation_sprite_draw(self->currentAnimation, &self->transform); +} + +void EnemyDestroy(Enemy* self) { + state_machine_destroy(self->behaviour); + // unregister *before* deallocating physics components + physics_world_remove_entity(Enemy_as_PhysicsEntity(self)); + collider_destroy(self->collider); + rigidbody_destroy(self->rigidbody); + sprite_destroy(self->sprite); + + free(self); +} + +void EnemyOnCollision(Enemy* self, Collision collision) {} +void EnemyOnOverlap(Enemy* self, Collider* other) {} + +Transform* EnemyGetTransform(Enemy* self) { + return &self->transform; +} + +RigidBody* EnemyGetRigidBody(Enemy* self) { + return self->rigidbody; +} + +int EnemyDamage(Enemy* self, DamageEventData* data) { + // being stunned gives iframes + if(self->stun_time > game_time()) + return 0; + // subtract damage + self->health -= data->damageAmount; + // die if health drops below zero + if(self->health <= 0) { + // TODO: swap to death state instead + game_world_destroy_entity(Enemy_as_BehaviourEntity(self)); + return 1; + } else { + // store the most recent damage event + self->last_damage = *data; + // get stunned + self->stun_time = game_time() + data->stun; + // calculate which direction the damage is coming from + const float direction = (data->origin.x - self->transform.position.x) >= 0.0f ? 1.0f : -1.0f; + // face the direction damage is coming from + self->facing = direction; + // add knockback according to damage data in the calculated direction + rigidbody_add_impulse(self->rigidbody, MakeVector(-direction * data->knockback, 0), 0); + return 0; + } +} diff --git a/game/src/Enemy.h b/game/src/Enemy.h new file mode 100644 index 0000000..b81046c --- /dev/null +++ b/game/src/Enemy.h @@ -0,0 +1,70 @@ +#ifndef FIGHT_ENEMY_H +#define FIGHT_ENEMY_H + +#include "dictionary.h" +#include "mirror.h" +#include "transform.h" +#include "state_machine.h" +#include "rigidbody.h" +#include "physics_entity.h" +#include "behaviour_entity.h" +#include "collider.h" +#include "sprite.h" +#include "animation_sprite.h" + +#include "Damagable.h" +#include "EnemyStates.h" +#include "typeclass_helpers.h" + +typedef struct Enemy { + Transform transform; + + StateMachine* behaviour; + + RigidBody* rigidbody; + Collider* collider; + Collider* hitbox; + + Sprite* sprite; + + int facing; + + DamageEventData last_damage; + float stun_time; + + AnimationSprite* idleAnim; + AnimationSprite* walkAnim; + AnimationSprite* hurtAnim; + + AnimationSprite* currentAnimation; + + int health; +} Enemy; + +extern Enemy* MakeEnemy(); +extern BehaviourEntity SpawnEnemy(Dictionary* args); + +extern void EnemyStart(Enemy* self); +extern void EnemyUpdate(Enemy* self, float deltaTime); +extern void EnemyDestroy(Enemy* self); + +extern void EnemyDraw(Enemy* self); + +extern void EnemyOnCollision(Enemy* self, Collision collision); +extern void EnemyOnOverlap(Enemy* self, Collider* other); + +extern Transform* EnemyGetTransform(Enemy* self); +extern RigidBody* EnemyGetRigidBody(Enemy* self); +static long EnemyGetDepth(Enemy* self) { return (long)(-self->transform.position.y * 1000); } + +extern int EnemyDamage(Enemy* self, DamageEventData* data); + +DECL_REFLECT(Enemy) + +decl_typeclass_impl(Transformable, Enemy) +decl_typeclass_impl(Drop, Enemy) +decl_typeclass_impl(BehaviourEntity, Enemy) +decl_typeclass_impl(PhysicsEntity, Enemy) +decl_typeclass_impl(Damagable, Enemy) + +#endif // !FIGHT_ENEMY_H diff --git a/game/src/EnemyStates.c b/game/src/EnemyStates.c new file mode 100644 index 0000000..db9028d --- /dev/null +++ b/game/src/EnemyStates.c @@ -0,0 +1,40 @@ +#include "EnemyStates.h" +#include "Enemy.h" +#include "animation_sprite.h" +#include "program.h" + +void EnemyState_Exit(Enemy* self) {} + +void EnemyIdle_Enter(Enemy* self) { + self->currentAnimation = self->idleAnim; + animation_sprite_play_from(self->currentAnimation, 0.f); +} + +const State* EnemyIdle_Update(Enemy* self, float deltaTime) { + // state transitions + if(self->stun_time >= game_time()) + return EnemyHurt(); + return EnemyIdle(); +} + +void EnemyWalk_Enter(Enemy* self) { + self->currentAnimation = self->walkAnim; + animation_sprite_play_from(self->currentAnimation, 0.f); +} + +const State* EnemyWalk_Update(Enemy* self, float deltaTime) { + return EnemyWalk(); +} + +void EnemyHurt_Enter(Enemy* self) { + self->currentAnimation = self->hurtAnim; + animation_sprite_play_from(self->currentAnimation, 0.f); +} + +const State* EnemyHurt_Update(Enemy* self, float deltaTime) { + const float time = animation_sprite_get_time(self->currentAnimation); + // state transitions + if(self->stun_time < game_time()) + return EnemyIdle(); + return EnemyHurt(); +} diff --git a/game/src/EnemyStates.h b/game/src/EnemyStates.h new file mode 100644 index 0000000..f493fc9 --- /dev/null +++ b/game/src/EnemyStates.h @@ -0,0 +1,37 @@ +#ifndef FIGHT_ENEMY_STATES_H +#define FIGHT_ENEMY_STATES_H + +#include "state.h" + +typedef struct Enemy Enemy; + +extern void EnemyState_Exit(Enemy* self); + +extern void EnemyIdle_Enter(Enemy* self); +extern const State* EnemyIdle_Update(Enemy* self, float deltaTime); + +DefineState(EnemyIdle, Enemy, + EnemyIdle_Enter, + EnemyIdle_Update, + EnemyState_Exit +) + +extern void EnemyWalk_Enter(Enemy* self); +extern const State* EnemyWalk_Update(Enemy* self, float deltaTime); + +DefineState(EnemyWalk, Enemy, + EnemyWalk_Enter, + EnemyWalk_Update, + EnemyState_Exit +) + +extern void EnemyHurt_Enter(Enemy* self); +extern const State* EnemyHurt_Update(Enemy* self, float deltaTime); + +DefineState(EnemyHurt, Enemy, + EnemyHurt_Enter, + EnemyHurt_Update, + EnemyState_Exit +) + +#endif // !FIGHT_ENEMY_STATES_H diff --git a/game/src/Hurtbox.c b/game/src/Hurtbox.c new file mode 100644 index 0000000..69f3c21 --- /dev/null +++ b/game/src/Hurtbox.c @@ -0,0 +1,40 @@ +#include "Hurtbox.h" +#include "Damagable.h" +#include "transformable.h" +#include "transform.h" +#include "physics_entity.h" +#include "physics_world.h" +#include "collider.h" +#include "Layers.h" + +static inline +void Internal_HurtboxDealDamage(const Hurtbox* self, List* colliders, float min_y, float max_y) { + DamageEventData data = self->damage; + list_foreach(Collider**, collider, colliders) { + PhysicsEntity entity = collider_get_owner(*collider); + const IDamagable* damagable = mirror_get_typeclass(entity.data, entity.mirror, "Damagable"); + const Transform* transform = entity.transformable->get_transform(entity.data); + if(damagable && transform->position.y >= min_y && transform->position.y <= max_y) damagable->damage(entity.data, &data); + } +} + +int HurtboxCast(Hurtbox* self, float facing_dir, List *out) { + const IPhysicsEntity* physics_entity = mirror_get_typeclass(self->owner.data, self->owner.mirror, "PhysicsEntity"); + const ITransformable* transformable = mirror_get_typeclass(self->owner.data, self->owner.mirror, "Transformable"); + + if(physics_entity == NULL || transformable == NULL) + return -1; + + RigidBody* body = physics_entity->get_rigidbody(self->owner.data); + const Transform* transform = transformable->get_transform(self->owner.data); + self->damage.origin = transform->position; + + const Vector offset = vaddf(transform->position, MakeVector(facing_dir * self->offset.x, self->offset.y)); + List found = physics_world_box_query_all(offset, self->size, PHYSICS_LAYER_COMBAT, body); + Internal_HurtboxDealDamage(self, &found, transform->position.y - self->depth_extent, transform->position.y + self->depth_extent); + if(out == NULL) + list_empty(&found); + else + *out = found; + return found.len; +} diff --git a/game/src/Hurtbox.h b/game/src/Hurtbox.h new file mode 100644 index 0000000..f565bf9 --- /dev/null +++ b/game/src/Hurtbox.h @@ -0,0 +1,18 @@ +#ifndef FIGHT_HURTBOX_H +#define FIGHT_HURTBOX_H + +#include "Damagable.h" +#include "behaviour_entity.h" +#include "vmath.h" + +typedef struct Hurtbox { + BehaviourEntity owner; + DamageEventData damage; + Vector size; + Vector offset; + float depth_extent; +} Hurtbox; + +extern int HurtboxCast(Hurtbox* self, float facing_dir, List *out); + +#endif // !FIGHT_HURTBOX_H diff --git a/game/src/Layers.h b/game/src/Layers.h new file mode 100644 index 0000000..24814d9 --- /dev/null +++ b/game/src/Layers.h @@ -0,0 +1,7 @@ +#ifndef FIGHT_LAYERS_H +#define FIGHT_LAYERS_H + +#define PHYSICS_LAYER_CHARACTERS 0x2 +#define PHYSICS_LAYER_COMBAT 0x3 + +#endif // !FIGHT_LAYERS_H diff --git a/game/src/Player.c b/game/src/Player.c new file mode 100644 index 0000000..4829c65 --- /dev/null +++ b/game/src/Player.c @@ -0,0 +1,272 @@ +#include "Player.h" +#include "debug.h" +#include "dictionary.h" +#include "input_axis.h" +#include "physics_world.h" + +#include "PlayerStates.h" +#include "Layers.h" +#include "program.h" +#include "variant.h" + +const Vector PLAYER_SPEED = { 1.0f, 0.50f }; +static const float PLAYER_INPUT_RATE = 1.f/15.f; + +START_REFLECT(Player); + REFLECT_TYPECLASS(Player, Drop); + REFLECT_TYPECLASS(Player, PhysicsEntity); + REFLECT_TYPECLASS(Player, BehaviourEntity); + REFLECT_TYPECLASS(Player, Transformable); +END_REFLECT(Player); + +impl_Drop_for(Player, + DestroyPlayer +) + +impl_BehaviourEntity_for(Player, + PlayerStart, + PlayerUpdate, + PlayerDraw, + PlayerGetDepth +) + +impl_Transformable_for(Player, + PlayerGetTransform +) + +impl_PhysicsEntity_for(Player, + PlayerGetRigidBody, + PlayerOnCollision, + PlayerOnOverlap +) + +static inline +void Internal_PlayerInitInput(Player* self) { + playerinput_add(self->playerInput, + CompositeAxis1D_as_InputAxis(compositeaxis1d_from_keys(SDL_SCANCODE_A, SDL_SCANCODE_D)), + (InputDelegateFn)PlayerHorizontalInput); + playerinput_add(self->playerInput, + CompositeAxis1D_as_InputAxis(compositeaxis1d_from_keys(SDL_SCANCODE_S, SDL_SCANCODE_W)), + (InputDelegateFn)PlayerVerticalInput); + playerinput_add(self->playerInput, + KeyBind_as_InputAxis(keybind_new(SDL_SCANCODE_SPACE)), + (InputDelegateFn)PlayerJumpInput); + playerinput_add(self->playerInput, + KeyBind_as_InputAxis(keybind_new(SDL_SCANCODE_J)), + (InputDelegateFn)PlayerLightAttackInput); + playerinput_add(self->playerInput, + KeyBind_as_InputAxis(keybind_new(SDL_SCANCODE_K)), + (InputDelegateFn)PlayerHeavyAttackInput); +} + +Player* MakePlayer() { + Player* self = malloc(sizeof(Player)); + ASSERT_RETURN(self != NULL, NULL, "Failed to allocate space for new Player instance"); + + *self = (Player) { + .transform = IdentityTransform, + .height = 0.f, + + .rigidbody = NULL, + .physicsCollider = NULL, + .hitbox = NULL, + + .verticalVelocity = 0.f, + + .playerInput = playerinput_new(self, -1), + .moveInput = ZeroVector, + .attackInput = 0, + .animationTriggers = 0, + + .facing = 1, + + .sprite = sprite_new_empty(), + + .idle = NULL, + .walk = NULL, + .jump = NULL, + .jab_a = NULL, + .jab_b = NULL, + .kick_a = NULL, + .slash = NULL, + .air_heavy = NULL, + .slide = NULL, + + .animationStateMachine = NULL, + .boxes = malloc(sizeof(Hurtbox) * 3), + + .pushInputTimer = 0.f, + .inputLog = list_from_type(PlayerInputFrame), + }; + + self->rigidbody = rigidbody_make(Player_as_PhysicsEntity(self)); + // physics collider used for movement + self->physicsCollider = collider_new(Player_as_PhysicsEntity(self), shape_new((Vector[]){ + MakeVector(-0.2f, -0.065f), + MakeVector( 0.2f, -0.065f), + MakeVector( 0.2f, 0.065f), + MakeVector(-0.2f, 0.065f) + }, 4), 0, PHYSICS_LAYER_CHARACTERS, PHYSICS_LAYER_DEFAULT); + // hitbox is used for combat only + self->hitbox = collider_new(Player_as_PhysicsEntity(self), shape_new((Vector[]){ + MakeVector(-0.1f, -0.9f), + MakeVector( 0.1f, -0.9f), + MakeVector( 0.1f, 0.00f), + MakeVector(-0.1f, 0.00f) + }, 4), 1, PHYSICS_LAYER_COMBAT, 0x0); // empty mask and overlap means this does not detect collisions itself + + sprite_set_origin(self->sprite, MakeVector(0.45f, 0.925f)); + + // load and configure animations + self->idle = animation_sprite_new(self->sprite, spritesheet_load("assets/Player_Idle.png", IVectorFrom(512)), 1.5f, LoopMode_Loop, NULL, 0); + self->walk = animation_sprite_new(self->sprite, spritesheet_load("assets/Player_Walk.png", IVectorFrom(512)), 5.f, LoopMode_Loop, NULL, 0); + self->jump = animation_sprite_new(self->sprite, spritesheet_load("assets/Player_Jumping.png", IVectorFrom(512)), 1.f, LoopMode_Stop, NULL, 0); + self->jab_a = animation_sprite_new(self->sprite, spritesheet_load("assets/Player_Jab_A.png", IVectorFrom(512)), 10.f, LoopMode_Stop, NULL, 0); + self->jab_b = animation_sprite_new(self->sprite, spritesheet_load("assets/Player_Jab_B.png", IVectorFrom(512)), 10.f, LoopMode_Stop, NULL, 0); + self->kick_a = animation_sprite_new(self->sprite, spritesheet_load("assets/Player_Kick_A.png", IVectorFrom(512)), 12.f, LoopMode_Stop, NULL, 0); + self->slash = animation_sprite_new(self->sprite, spritesheet_load("assets/Player_Slash.png", IVectorFrom(512)), 12.f, LoopMode_Stop, NULL, 0); + self->air_heavy = animation_sprite_new(self->sprite, spritesheet_load("assets/Player_Air_Heavy.png", IVectorFrom(512)), 10.f, LoopMode_Stop, NULL, 0); + self->slide = animation_sprite_new(self->sprite, spritesheet_load("assets/Player_Slide.png", IVectorFrom(512)), 1.f, LoopMode_Loop, NULL, 0); + + self->animationStateMachine = state_machine_init(self, PlayerIdle()); + + return self; +} + +BehaviourEntity SpawnPlayer(Dictionary* args) { + Variant arg; + Player* self = MakePlayer(); + Internal_PlayerInitInput(self); + if(dictionary_try_get(args, "position", &arg) && arg.type == Variant_Vector) + self->transform.position = arg.as_vector; + return Player_as_BehaviourEntity(self); +} + +void DestroyPlayer(Player* self) { + // deregister and free physics components + physics_world_remove_entity(Player_as_PhysicsEntity(self)); + collider_destroy(self->physicsCollider); + rigidbody_destroy(self->rigidbody); + + playerinput_drop(self->playerInput); + + // erase animations + animation_sprite_destroy(self->idle); + animation_sprite_destroy(self->walk); + animation_sprite_destroy(self->jump); + animation_sprite_destroy(self->jab_a); + animation_sprite_destroy(self->jab_b); + animation_sprite_destroy(self->kick_a); + animation_sprite_destroy(self->air_heavy); + animation_sprite_destroy(self->slide); + sprite_destroy(self->sprite); + + state_machine_destroy(self->animationStateMachine); +} + +void PlayerStart(Player* self) { + animation_sprite_play_from(self->currentAnimation, 0.0f); +} + +static +void PlayerPushInput(Player* self) { + // the current input state + PlayerInputFrame state = { + .time = game_time(), + .direction = self->moveInput, + .light = self->attackInput == 1, + .heavy = self->attackInput == 2, + .jump = self->jumpInput, + .facing = self->facing, + }; + // push onto the end of the log + list_add(&self->inputLog, &state); + // erase oldest input from the log + if(self->inputLog.len >= 5) + list_erase(&self->inputLog, 0); + // log current input state + LOG_INFO("%f %f, L:%x, H:%x, J:%x", + state.direction.x, state.direction.y, + state.light, state.heavy, state.jump); +} + +void PlayerUpdate(Player* self, float deltaTime) { + // update state machine + state_machine_update(self->animationStateMachine, deltaTime); + // update gravity + self->height += self->verticalVelocity * deltaTime; + if(self->height <= 0.f) + self->height = 0.f; +} + +void PlayerDraw(Player* self) { + // create a new transform that adjusts the "ground" transform to reflect distance from the ground as well + Transform trans = self->transform; + trans.position.y -= self->height; + animation_sprite_draw(self->currentAnimation, &trans); +} + +void PlayerHorizontalInput(Player* self, InputEvent value) { + self->moveInput.x = value.as_float; + PlayerPushInput(self); +} + +void PlayerVerticalInput(Player* self, InputEvent value) { + self->moveInput.y = -value.as_float; + PlayerPushInput(self); +} + +void PlayerJumpInput(Player* self, InputEvent value) { + if(value.as_bool) { + self->jumpInput = value.as_bool; + PlayerPushInput(self); + } +} + +void PlayerLightAttackInput(Player* self, InputEvent value) { + if(value.as_bool) { + self->attackInput = 1; + PlayerPushInput(self); + } +} + +void PlayerHeavyAttackInput(Player* self, InputEvent value) { + if(value.as_bool) { + self->attackInput = 2; + PlayerPushInput(self); + } +} + +void PlayerOnCollision(Player* self, Collision collision) {} +void PlayerOnOverlap(Player* self, Collider* other) {} + +Transform* PlayerGetTransform(Player* self) { + return &self->transform; +} + +RigidBody* PlayerGetRigidBody(Player* self) { + return self->rigidbody; +} + +PlayerInputFrame* PlayerInputHistory(Player* self) { + return (PlayerInputFrame*)self->inputLog.data; +} + +int PlayerInputIsQuarterCircleForward(Player* self) { + // at least four inputs have to be logged for a valid quartercircle + // (three directional, one button, in that order) + if(self->inputLog.len < 4) + return 0; + PlayerInputFrame* history = PlayerInputHistory(self); + // the most recent input (assumed to be a button input) + const size_t last = self->inputLog.len-1; + // the forward direction at the assumed start of the action + const float forward = history[last-3].facing; + if(game_time() - history[last-2].time > 0.225f) + return 0; + // check if the three inputs before the most recent input are a quartercircle towards the facing direction at last-3 (three before current) + // current (history[last]) is assumed to be a button input + return veqf(history[last-3].direction, MakeVector(0.f, 1.f)) + && veqf(history[last-2].direction, MakeVector(forward, 1.f)) + && veqf(history[last-1].direction, MakeVector(forward, 0.f)); +} diff --git a/game/src/Player.h b/game/src/Player.h new file mode 100644 index 0000000..2f4d54b --- /dev/null +++ b/game/src/Player.h @@ -0,0 +1,100 @@ +#ifndef FIGHT_PLAYER_H +#define FIGHT_PLAYER_H + +#include "Hurtbox.h" +#include "dictionary.h" +#include "state_machine.h" +#include "mirror.h" +#include "behaviour_entity.h" +#include "animation_sprite.h" +#include "vmath.h" +#include "transform.h" +#include "player_input.h" +#include "rigidbody.h" +#include "collider.h" +#include "typeclass_helpers.h" + +extern const Vector PLAYER_SPEED; + +typedef struct PlayerInputFrame { + float time; + Vector direction; + int light; + int heavy; + int jump; + float facing; +} PlayerInputFrame; + +typedef struct Player { + Transform transform; + float height; + + RigidBody* rigidbody; + Collider* physicsCollider; + Collider* hitbox; + + float verticalVelocity; + + PlayerInput* playerInput; + + Vector moveInput; + int attackInput; + int jumpInput; + size_t animationTriggers; + + int facing; + + Sprite* sprite; + + AnimationSprite* idle; + AnimationSprite* walk; + AnimationSprite* jump; + AnimationSprite* jab_a; + AnimationSprite* jab_b; + AnimationSprite* kick_a; + AnimationSprite* slash; + AnimationSprite* air_heavy; + AnimationSprite* slide; + + StateMachine* animationStateMachine; + Hurtbox *boxes; + + AnimationSprite* currentAnimation; + float pushInputTimer; + List inputLog; + PlayerInputFrame nextInputFrame; +} Player; + +Player* MakePlayer(); +BehaviourEntity SpawnPlayer(Dictionary* args); +void DestroyPlayer(Player* self); + +void PlayerStart(Player* self); +void PlayerUpdate(Player* self, float deltaTime); +void PlayerDraw(Player* self); + +void PlayerHorizontalInput(Player* self, InputEvent value); +void PlayerVerticalInput(Player* self, InputEvent value); +void PlayerJumpInput(Player* self, InputEvent value); +void PlayerLightAttackInput(Player* self, InputEvent value); +void PlayerHeavyAttackInput(Player* self, InputEvent value); + +void PlayerOnCollision(Player* self, Collision collision); +void PlayerOnOverlap(Player* self, Collider* other); + +Transform* PlayerGetTransform(Player* self); +RigidBody* PlayerGetRigidBody(Player* self); + +PlayerInputFrame* PlayerInputHistory(Player* self); + +int PlayerInputIsQuarterCircleForward(Player* self); + +static long PlayerGetDepth(Player* self) { return (int)(-10-self->transform.position.y * 1000); } + +DECL_REFLECT(Player); +decl_typeclass_impl(BehaviourEntity, Player) +decl_typeclass_impl(Drop, Player) +decl_typeclass_impl(Transformable, Player) +decl_typeclass_impl(PhysicsEntity, Player) + +#endif // !FIGHT_PLAYER_H diff --git a/game/src/PlayerStates.c b/game/src/PlayerStates.c new file mode 100644 index 0000000..259090f --- /dev/null +++ b/game/src/PlayerStates.c @@ -0,0 +1,262 @@ +#include "PlayerStates.h" +#include "Hurtbox.h" + +#include "Damagable.h" +#include "Player.h" +#include "animation_sprite.h" + +// flip the facing direction of the player based on input +// does not do movement +static inline +void InternalSpriteFlipWithMovement(Player* self) { + if(self->moveInput.x > 0.f) + self->facing = 1; + if(self->moveInput.x < -0.1f) + self->facing = -1; + sprite_flip_horizontal(self->sprite, self->facing != 1); +} + +void PlayerAnimationExit(Player* self) { + self->animationTriggers = 0; + self->attackInput = 0; +} + +static +const State* PlayerTryStartNewChain(Player* self, const State* fallback) { + if(self->attackInput == 1) { + return PlayerJabA(); + } + if(self->attackInput == 2) { + if(self->height > 0.f) + return PlayerAirHeavy(); + else if(PlayerInputIsQuarterCircleForward(self)) + return PlayerSlide(); + else + return PlayerSlash(); + } + return fallback; +} + +void PlayerIdleEnter(Player* self) { + self->currentAnimation = self->idle; + rigidbody_set_velocity(self->rigidbody, ZeroVector); + animation_sprite_play_from(self->currentAnimation, 0.f); +} + +const State* PlayerIdleUpdate(Player* self, float deltaTime) { + if(!veqf(self->moveInput, ZeroVector)) + return PlayerWalk(); + if(self->jumpInput) + return PlayerJump(); + return PlayerTryStartNewChain(self, PlayerIdle()); +} + +void PlayerWalk_Enter(Player* self) { + self->currentAnimation = self->walk; + animation_sprite_play_from(self->currentAnimation, 0.f); +} + +const State* PlayerWalk_Update(Player* self, float deltaTime) { + rigidbody_set_velocity(self->rigidbody, vmulf(vnormalizedf(self->moveInput), PLAYER_SPEED)); + + if(veqf(self->moveInput, ZeroVector)) + return PlayerIdle(); + if(self->jumpInput) + return PlayerJump(); + InternalSpriteFlipWithMovement(self); + return PlayerTryStartNewChain(self, PlayerWalk()); +} + +void PlayerAttackEnter(Player* self) { + self->attackInput = 0; + rigidbody_set_velocity(self->rigidbody, ZeroVector); + InternalSpriteFlipWithMovement(self); +} + +static +void PlayerHurtbox(Player* self, DamageEventData damage, Vector hitbox_size, Vector offset) { + Hurtbox box = { + .owner = Player_as_BehaviourEntity(self), + .damage = damage, + .size = hitbox_size, + .offset = vaddf(offset, MakeVector(0.f, self->height)), + .depth_extent = 0.15f, + }; + HurtboxCast(&box, self->facing, NULL); +} + +void PlayerJabA_Enter(Player* self) { + PlayerAttackEnter(self); + self->currentAnimation = self->jab_a; + animation_sprite_play_from(self->currentAnimation, 0.f); +} + +const State* PlayerJabA_Update(Player* self, float deltaTime) { + const static DamageEventData damage = { + .damageAmount = 1, + .knockdown = 0, + .stun = 0.15f, + .knockback = 7.0f, + }; + const float ntime = animation_sprite_get_time_normalized(self->currentAnimation); + if(self->animationTriggers == 0 && ntime > 0.33f) { + PlayerHurtbox(self, damage, MakeVector(0.1f, 0.1f), MakeVector(0.265f, -0.6f)); + ++self->animationTriggers; + } + if(ntime > 1.0f) { + if(self->attackInput == 1) + return PlayerJabB(); + else if(self->attackInput == 2) + return PlayerKickA(); + } + if(!veqf(self->moveInput, ZeroVector) && ntime > 1.05f) + return PlayerWalk(); + if(ntime >= 1.5f) + return PlayerIdle(); + return PlayerJabA(); +} + +void PlayerJabB_Enter(Player* self) { + PlayerAttackEnter(self); + self->currentAnimation = self->jab_b; + animation_sprite_play_from(self->currentAnimation, 0.f); +} + +const State* PlayerJabB_Update(Player* self, float deltaTime) { + static const DamageEventData damage = { + .damageAmount = 1, + .knockdown = 0, + .stun = 0.15f, + .knockback = 7.0f, + }; + const float ntime = animation_sprite_get_time_normalized(self->currentAnimation); + const size_t frame = sprite_get_tile(self->sprite); + if(self->animationTriggers == 0 && ntime > 0.33f) { + PlayerHurtbox(self, damage, MakeVector(0.13f, 0.05f), MakeVector(0.32f, -0.7f)); + ++self->animationTriggers; + } + if(ntime > 1.0f && self->attackInput == 1) + return PlayerKickA(); + if(!veqf(self->moveInput, ZeroVector) && ntime > 1.05f) + return PlayerWalk(); + if(ntime >= 2.0f) + return PlayerIdle(); + return PlayerJabB(); +} + +void PlayerKickA_Enter(Player* self) { + PlayerAttackEnter(self); + self->currentAnimation = self->kick_a; + animation_sprite_play_from(self->currentAnimation, 0.f); +} + +const State* PlayerKickA_Update(Player* self, float deltaTime) { + static const DamageEventData damage = { + .damageAmount = 2, + .knockdown = 1, + .stun = 0.5f, + .knockback = 15.0f, + }; + const float ntime = animation_sprite_get_time_normalized(self->currentAnimation); + const size_t frame = sprite_get_tile(self->sprite); + if(frame >= 3 && frame <= 4) { + PlayerHurtbox(self, damage, MakeVector(0.16f, 0.06f), MakeVector(0.33f, -0.4f)); + } + if(ntime >= 1.f) + return PlayerIdle(); + return PlayerKickA(); +} + +void PlayerSlash_Enter(Player* self) { + PlayerAttackEnter(self); + self->currentAnimation = self->slash; + animation_sprite_play_from(self->currentAnimation, 0.f); +} + +const State* PlayerSlash_Update(Player* self, float deltaTime) { + static const DamageEventData damage = { + .damageAmount = 2, + .knockdown = 0, + .stun = 0.5f, + .knockback = 4.f + }; + const float ntime = animation_sprite_get_time_normalized(self->currentAnimation); + const size_t frame = sprite_get_tile(self->sprite); + if(frame >= 2 && self->animationTriggers == 0) { + ++self->animationTriggers; + PlayerHurtbox(self, damage, MakeVector(0.4f, 0.1f), MakeVector(0.2f, -0.7f)); + } + if(!veqf(self->moveInput, ZeroVector) && ntime > 1.05f) + return PlayerWalk(); + if(ntime >= 1.f) + return PlayerIdle(); + return PlayerSlash(); + +} + +void PlayerSlide_Enter(Player* self) { + PlayerAttackEnter(self); + self->currentAnimation = self->slide; + // adjust for the downward motion of the quartercircle + Transform* trans = rigidbody_get_transform(self->rigidbody); + trans->position.y -= 0.03f; + animation_sprite_play_from(self->currentAnimation, 0.f); +} + +const State* PlayerSlide_Update(Player* self, float deltaTime) { + static const DamageEventData damage = { + .damageAmount = 3, + .knockdown = 1, + .stun = 0.1f, + .knockback = 17.0f, + }; + static const float duration = 0.2f; + static const float speed = 4.f; + const float time = animation_sprite_get_time(self->currentAnimation); + rigidbody_set_velocity(self->rigidbody, MakeVector(self->facing * speed, -0.05f)); + if(time > duration) + return PlayerIdle(); + if(time > 0.1f) { + PlayerHurtbox(self, damage, MakeVector(0.2f, 0.15f), MakeVector(0.21f, -0.6f)); + } + return PlayerSlide(); +} + +void PlayerJump_Enter(Player* self) { + if(self->jumpInput) { + self->jumpInput = 0; + self->currentAnimation = self->jump; + self->verticalVelocity = 3.f; + } + animation_sprite_play_from(self->currentAnimation, 0.f); +} + +const State* PlayerJump_Update(Player* self, float deltaTime) { + self->verticalVelocity -= 5.f * deltaTime; + if(self->height == 0.f) { + self->verticalVelocity = 0.f; + return PlayerIdle(); + } + if(self->attackInput == 2) + return PlayerAirHeavy(); + return PlayerJump(); +} + +void PlayerAirHeavy_Enter(Player* self) { + self->currentAnimation = self->air_heavy; + animation_sprite_play_from(self->currentAnimation, 0.f); +} + +const State* PlayerAirHeavy_Update(Player* self, float deltaTime) { + const static DamageEventData damage = { + .damageAmount = 4, + .knockdown = 1, + .stun = 0.75f, + .knockback = 0.15f + }; + const float ntime = animation_sprite_get_time_normalized(self->currentAnimation); + size_t frame = sprite_get_tile(self->sprite); + if(frame == 1) PlayerHurtbox(self, damage, MakeVector(0.12f, 0.12f), MakeVector(0.12f, -0.48f)); + const State* result = PlayerJump_Update(self, deltaTime); + return (result == PlayerJump()) ? PlayerAirHeavy() : result; +} diff --git a/game/src/PlayerStates.h b/game/src/PlayerStates.h new file mode 100644 index 0000000..0e6633c --- /dev/null +++ b/game/src/PlayerStates.h @@ -0,0 +1,93 @@ +#ifndef FIGHT_PLAYER_STATES_H +#define FIGHT_PLAYER_STATES_H + +#include "state.h" + +typedef struct Player Player; + +extern void PlayerAnimationExit(Player* self); + +extern void PlayerIdleEnter(Player* self); +extern const State* PlayerIdleUpdate(Player* self, float deltaTime); + +DefineState(PlayerIdle, Player, + PlayerIdleEnter, + PlayerIdleUpdate, + PlayerAnimationExit +) + +extern void PlayerWalk_Enter(Player* self); +extern const State* PlayerWalk_Update(Player* self, float deltaTime); + +DefineState(PlayerWalk, Player, + PlayerWalk_Enter, + PlayerWalk_Update, + PlayerAnimationExit +) + +extern void PlayerAttackEnter(Player* self); + +extern void PlayerJabA_Enter(Player* self); +extern const State* PlayerJabA_Update(Player* self, float deltaTime); + +DefineState(PlayerJabA, Player, + PlayerJabA_Enter, + PlayerJabA_Update, + PlayerAnimationExit +) + +extern void PlayerJabB_Enter(Player* self); +extern const State* PlayerJabB_Update(Player* self, float deltaTime); + +DefineState(PlayerJabB, Player, + PlayerJabB_Enter, + PlayerJabB_Update, + PlayerAnimationExit +) + +extern void PlayerKickA_Enter(Player* self); +extern const State* PlayerKickA_Update(Player* self, float deltaTime); + +DefineState(PlayerKickA, Player, + PlayerKickA_Enter, + PlayerKickA_Update, + PlayerAnimationExit +) + +extern void PlayerSlash_Enter(Player* self); +extern const State* PlayerSlash_Update(Player* self, float deltaTime); + +DefineState(PlayerSlash, Player, + PlayerSlash_Enter, + PlayerSlash_Update, + PlayerAnimationExit +) + +extern void PlayerSlide_Enter(Player* self); +extern const State* PlayerSlide_Update(Player* self, float deltaTime); + +DefineState(PlayerSlide, Player, + PlayerSlide_Enter, + PlayerSlide_Update, + PlayerAnimationExit +) + +extern void PlayerJump_Enter(Player *self); +extern const State* PlayerJump_Update(Player* self, float deltaTime); + +DefineState(PlayerJump, Player, + PlayerJump_Enter, + PlayerJump_Update, + PlayerAnimationExit +) + +extern void PlayerAirHeavy_Enter(Player* self); +extern const State* PlayerAirHeavy_Update(Player* self, float deltaTime); + +DefineState(PlayerAirHeavy, Player, + PlayerAirHeavy_Enter, + PlayerAirHeavy_Update, + PlayerAnimationExit +) + +#endif // !FIGHT_PLAYER_STATES_H diff --git a/game/src/Prop.c b/game/src/Prop.c new file mode 100644 index 0000000..6cd5a74 --- /dev/null +++ b/game/src/Prop.c @@ -0,0 +1,89 @@ +#include "Prop.h" +#include "debug.h" +#include "game_world.h" +#include "mirror.h" +#include "physics_world.h" + +START_REFLECT(Prop); + REFLECT_TYPECLASS(Prop, Transformable); + REFLECT_TYPECLASS(Prop, Drop); + REFLECT_TYPECLASS(Prop, BehaviourEntity); +END_REFLECT(Prop); + +impl_Transformable_for(Prop, + PropGetTransform +) + +impl_PhysicsEntity_for(Prop, + PropGetRigidBody, + PropOnCollision, + PropOnOverlap +) + +impl_Drop_for(Prop, + DestroyProp +) + +impl_BehaviourEntity_for(Prop, + PropStart, + PropUpdate, + PropDraw, + PropGetDepth +) + +Prop* MakeProp(Sprite* sprite, Shape* shape) { + Prop* self = malloc(sizeof(Prop)); + ASSERT_RETURN(self != NULL, NULL, "Failed to allocate space for Prop instance"); + *self = (Prop) { + .transform = IdentityTransform, + .sprite = sprite, + .rigidbody = NULL, + .collisionShape = NULL + }; + self->rigidbody = rigidbody_make(Prop_as_PhysicsEntity(self)); + self->collisionShape = collider_new(Prop_as_PhysicsEntity(self), shape, 0, PHYSICS_LAYER_DEFAULT, PHYSICS_LAYER_DEFAULT); + rigidbody_set_static(self->rigidbody, 1); + sprite_set_origin(self->sprite, MakeVector(0.5f, 1.0f)); + return self; +} + +Prop* SpawnProp(Vector location, Sprite* sprite, Shape* shape, Vector origin) { + Prop* self = MakeProp(sprite, shape); + + self->transform.position + = rigidbody_get_transform(self->rigidbody)->position + = location; + + game_world_add_entity(Prop_as_BehaviourEntity(self)); + physics_world_add_entity(Prop_as_PhysicsEntity(self)); + + sprite_set_origin(self->sprite, origin); + + return self; +} + +void DestroyProp(Prop* self) { + sprite_destroy(self->sprite); + physics_world_remove_entity(Prop_as_PhysicsEntity(self)); + collider_destroy(self->collisionShape); + rigidbody_destroy(self->rigidbody); + free(self); +} + +void PropStart(Prop* self) {} +void PropUpdate(Prop* self, float deltaTime) {} + +void PropDraw(Prop* self) { + sprite_draw(self->sprite, self->transform); +} + +void PropOnCollision(Prop* self, Collision collision) {} +void PropOnOverlap(Prop* self, Collider* other) {} + +Transform* PropGetTransform(Prop* self) { + return &self->transform; +} + +RigidBody* PropGetRigidBody(Prop* self) { + return self->rigidbody; +} diff --git a/game/src/Prop.h b/game/src/Prop.h new file mode 100644 index 0000000..e3b0ee7 --- /dev/null +++ b/game/src/Prop.h @@ -0,0 +1,44 @@ +#ifndef FIGHT_PROP_H +#define FIGHT_PROP_H + +#include "physics_entity.h" +#include "behaviour_entity.h" +#include "transformable.h" +#include "drop.h" +#include "transform.h" +#include "sprite.h" +#include "rigidbody.h" +#include "collider.h" +#include "typeclass_helpers.h" + +typedef struct Prop { + Transform transform; + Sprite* sprite; + RigidBody* rigidbody; + Collider* collisionShape; +} Prop; + +Prop* MakeProp(Sprite* sprite, Shape* shape); +Prop* SpawnProp(Vector location, Sprite* sprite, Shape* shape, Vector origin); +void DestroyProp(Prop* self); + +void PropStart(Prop* self); +void PropUpdate(Prop* self, float deltaTime); +void PropDraw(Prop* self); + +void PropOnCollision(Prop* self, Collision collision); +void PropOnOverlap(Prop* self, Collider* other); + +Transform* PropGetTransform(Prop* self); +RigidBody* PropGetRigidBody(Prop* self); + +static long PropGetDepth(Prop* self) { return -(int)(self->transform.position.y * 1000); } + +DECL_REFLECT(Prop) + +decl_typeclass_impl(Transformable, Prop) +decl_typeclass_impl(PhysicsEntity, Prop) +decl_typeclass_impl(Drop, Prop) +decl_typeclass_impl(BehaviourEntity, Prop) + +#endif // !FIGHT_PROP_H diff --git a/premake5.lua b/premake5.lua deleted file mode 100644 index e97d585..0000000 --- a/premake5.lua +++ /dev/null @@ -1,20 +0,0 @@ -workspace "fencer" - configurations { "Debug", "Release" } - location "." - -project "fencer" - kind "WindowedApp" - language "C" - location "build/" - files { "src/**.c" } - links { "SDL2", "SDL2_image", "cjson", "m" } - buildoptions { "-Wall", "-DVMATH_SDL=1" } - targetdir "bin/" - filter "configurations:Debug" - defines { "DEBUG" } - buildoptions { "-g3" } - symbols "On" - filter "configurations:Release" - buildoptions { "-g0" } - defines { "NDEBUG" } - optimize "On" diff --git a/src/collision.h b/src/collision.h deleted file mode 100644 index 188e8e0..0000000 --- a/src/collision.h +++ /dev/null @@ -1,24 +0,0 @@ -#ifndef _fencer_collision_h -#define _fencer_collision_h - -#include "shape.h" -#include "physics_entity.h" -#include - -typedef struct Collision { - PhysicsEntity other; - - Vector point; - Vector normal; - - Vector velocity; - Vector penetration_vector; - - Vector edge_left; - Vector edge_right; -} Collision; - -extern Collision collision_invert(Collision src, PhysicsEntity new_other); -extern int collision_check(PhysicsEntity a, PhysicsEntity b, Collision* out_a, Collision* out_b); - -#endif // !_fencer_collision_h diff --git a/src/debug.c b/src/debug.c deleted file mode 100644 index 4f9d6aa..0000000 --- a/src/debug.c +++ /dev/null @@ -1,9 +0,0 @@ -#include "debug.h" - -#if NDEBUG -int g_debug_error_abort = 0; -int g_debug_log_lvl = 0; -#else -int g_debug_error_abort = 1; -int g_debug_log_lvl = 3; -#endif diff --git a/src/debug.h b/src/debug.h deleted file mode 100644 index 1095262..0000000 --- a/src/debug.h +++ /dev/null @@ -1,69 +0,0 @@ -#ifndef _fencer_debug_h -#define _fencer_debug_h - -#include -#include - -#ifndef NDEBUG -#include -#endif - -extern int g_debug_error_abort; -extern int g_debug_log_lvl; - -#define LOG_INFO(...) do {\ - if(g_debug_log_lvl < 3) break;\ - printf("[%s:%d] INFO | ", __FILE__, __LINE__);\ - printf(__VA_ARGS__);\ - printf("\n");\ -} while(0) - -#define LOG_ERROR(...) do {\ - if(g_debug_log_lvl >= 1) {\ - printf("[%s:%d] ERROR | ", __FILE__, __LINE__);\ - printf(__VA_ARGS__);\ - printf("\n");\ - fflush(stdout);\ - }\ - if(g_debug_error_abort != 0) abort();\ -} while(0) - -#define LOG_WARNING(...) do {\ - if(g_debug_log_lvl < 2) break;\ - printf("[%s:%d] WARNING | ", __FILE__, __LINE__);\ - printf(__VA_ARGS__);\ - printf("\n");\ -} while(0) - - -#define RETURN_ERROR(__VALUE, ...) do {\ - LOG_ERROR(__VA_ARGS__);\ - return __VALUE;\ -} while(0) - -#define RETURN_WARNING(__VALUE, ...) do {\ - LOG_WARNING(__VA_ARGS__);\ - return __VALUE;\ -} while(0) - -#define ASSERT_RETURN(__ASSERT, __RETURN, ...) do {\ - if(!(__ASSERT)) {\ - LOG_ERROR(__VA_ARGS__);\ - return __RETURN;\ - }\ -} while(0) - -#define CHECK(__ASSERT, ...) do {\ - if(!(__ASSERT)) {\ - LOG_ERROR(__VA_ARGS__);\ - }\ -} while(0) - -#define ASSERT_RETURN_WARN(__ASSERT, __RETURN, ...) do {\ - if(!(__ASSERT)) {\ - LOG_WARNING(__VA_ARGS__);\ - return __RETURN;\ - }\ -} while(0) - -#endif // !_fencer_debug_h diff --git a/src/drop.h b/src/drop.h deleted file mode 100644 index e5aaef4..0000000 --- a/src/drop.h +++ /dev/null @@ -1,26 +0,0 @@ -#ifndef _fencer_drop_h -#define _fencer_drop_h - -typedef struct { - void (*const drop)(void* self); -} IDrop; - -typedef struct { - void const* data; - IDrop const* tc; -} Drop; - -#define impl_Drop_for(T, drop_f)\ -static inline Drop T##_as_Drop(T* x) {\ - TC_FN_TYPECHECK(void, drop_f, T*);\ - static IDrop const tc = {\ - .drop = (void(*const)(void*)) drop_f,\ - };\ - return (Drop){.tc = &tc, .data = x};\ -} - -#define impl_default_Drop_for(T)\ - static void default_drop_##T(T* v) { free(v); }\ - impl_Drop_for(T, default_drop_##T) - -#endif // !_fencer_drop_h diff --git a/src/list.c b/src/list.c deleted file mode 100644 index 6ec9a6a..0000000 --- a/src/list.c +++ /dev/null @@ -1,161 +0,0 @@ -#include "list.h" -#include "stdint.h" -#include "stdlib.h" -#include "string.h" -#include "debug.h" - -#ifndef LIST_DEFAULT_RESERVE -#define LIST_DEFAULT_RESERVE 4 -#endif - -List list_init(size_t element_size) { - List self = { - .element_size = element_size, - .cap = LIST_DEFAULT_RESERVE, - .len = 0, - .data = malloc(element_size * LIST_DEFAULT_RESERVE), - }; - - if(self.data == NULL) { - LOG_ERROR("Failed to allocate list with starting capacity of %d", LIST_DEFAULT_RESERVE); - self.cap = 0; - } - - return self; -} - -List list_copy(const List* source) { - List self = list_init(source->element_size); - list_reserve(&self, source->cap); - if(self.cap > 0) { - memcpy(self.data, source->data, source->element_size * source->len); - self.len = source->len; - } - return self; -} - -void list_empty(List* self) { - if(self->data == NULL || self->cap == 0) - return; - self->data = NULL; - self->cap = 0; - self->len = 0; -} - -void list_reserve(List* self, size_t at_least) { - if(at_least < self->cap) - return; - - size_t new_cap = self->cap > 0 ? self->cap : LIST_DEFAULT_RESERVE; - while(at_least >= new_cap) { - new_cap *= 2; - } - - void* new; - if(self->data == NULL) - new = malloc(new_cap * self->element_size); - else - new = realloc(self->data, new_cap * self->element_size); - ASSERT_RETURN(new != NULL,, "Failed to reserve space for %zu extra elements in list", new_cap); - - self->data = new; - self->cap = new_cap; -} - -static inline -void* list_at_unchecked(List* self, size_t at) { - union { - uint8_t* as_byte; - void* as_void; - } data = { - .as_void = self->data - }; - - return data.as_byte + self->element_size * at; -} - -void* list_at(List* self, size_t at) { - ASSERT_RETURN(at < self->len, NULL, "Index %zu out of bounds", at); - return list_at_unchecked(self, at); -} - -size_t list_add(List* self, void* item) { - list_reserve(self, self->len + 1); - union { - uint8_t* as_byte; - void* as_void; - } data = { - .as_void = self->data - }; - - uint8_t* into = data.as_byte + self->element_size * self->len; - - memcpy(into, item, self->element_size); - ++self->len; - - return self->len - 1; -} - -void list_insert(List* self, void* item, size_t at) { - list_reserve(self, self->len + 1); - - if(at == self->len - 1) { - list_add(self, item); - return; - } - - union { - uint8_t* as_byte; - void* as_void; - } data = { - .as_void = self->data - }; - uint8_t* from = data.as_byte + self->element_size * at; - uint8_t* into = data.as_byte + self->element_size * (at + 1); - uint8_t* end = data.as_byte + self->element_size * self->len; - memmove(into, from, end - from); - memcpy(from, item, self->element_size); - ++self->len; -} - -void list_erase(List* self, size_t at) { - ASSERT_RETURN(at < self->len,, "Index %zu out of bounds", at); - - union { - uint8_t* as_byte; - void* as_void; - } data = { - .as_void = self->data - }; - - uint8_t* into = data.as_byte + at * self->element_size; - uint8_t* from = data.as_byte + (at + 1) * self->element_size; - - if(at < self->len - 1) - memmove(into, from, (self->len - at) * self->element_size); - --self->len; - - size_t new_cap = self->cap; - while(new_cap > self->len) { - new_cap /= 2; - } - new_cap *= 2; - - - if(new_cap == self->cap) - return; - - void* shrunk = realloc(self->data, new_cap * self->element_size); - ASSERT_RETURN(shrunk != NULL || new_cap == 0,, "Failed to shrink List to %zu", new_cap); - - self->data = shrunk; - self->cap = new_cap; -} - -void* list_iterator_begin(List* self) { - return list_at_unchecked(self, 0); -} - -void* list_iterator_end(List* self) { - return list_at_unchecked(self, self->len); -} diff --git a/src/list.h b/src/list.h deleted file mode 100644 index 6f8789f..0000000 --- a/src/list.h +++ /dev/null @@ -1,35 +0,0 @@ -#ifndef _fencer_list_h -#define _fencer_list_h - -#include "stddef.h" - -typedef struct List List; -struct List { - void* data; - size_t cap; - size_t len; - - size_t element_size; -}; - -extern List list_init(size_t element_size); -extern List list_copy(const List* source); -extern void list_empty(List* list); - -extern void list_reserve(List* self, size_t at_least); -extern void* list_at(List* list, size_t at); - -extern size_t list_add(List* self, void* item); -extern void list_insert(List* self, void* item, size_t at); -extern void list_erase(List* self, size_t at); - -extern void* list_iterator_begin(List* self); -extern void* list_iterator_end(List* self); - -#define list_from_type(T) list_init(sizeof(T)) -#define list_foreach(T, iter, list) for(T iter = list_iterator_begin(list); iter != (T)list_iterator_end(list); ++iter) -#define list_at_as(T, __list, __i) ((T*)(list_at(__list, __i))) - -#define list_iterator_begin_as(T, __list) ((T*)(list_iterator_begin(__list))) - -#endif // !_fencer_list_h diff --git a/src/physics_world.c b/src/physics_world.c deleted file mode 100644 index 2b95ef4..0000000 --- a/src/physics_world.c +++ /dev/null @@ -1,68 +0,0 @@ -#include "physics_world.h" -#include "debug.h" -#include "collision.h" -#include "rigidbody.h" - -static List _world_bodies; - -void physics_world_init() { - _world_bodies = list_from_type(PhysicsEntity); -} - -void physics_world_clean() { - list_empty(&_world_bodies); -} - -void physics_world_add_entity(PhysicsEntity entity) { - list_add(&_world_bodies, &entity); -} - -void physics_world_remove_entity(PhysicsEntity entity) { - for(size_t i = 0; i < _world_bodies.len; ++i) { - PhysicsEntity* found = list_at_as(PhysicsEntity, &_world_bodies, i); - if(found->data == entity.data) { - list_erase(&_world_bodies, i); - return; - } - } - - ASSERT_RETURN(0,, "Physics entity with data at %p is not registered in physics world", entity.data); -} - -static inline -void _internal_physics_narrow_collision() { - size_t half_end = _world_bodies.len/2; - Collision collision_left, collision_right; - - PhysicsEntity* right = NULL; - list_foreach(PhysicsEntity*, left, &_world_bodies) { - for(size_t right_index = 0; right_index < half_end; ++right_index) { - right = list_at_as(PhysicsEntity, &_world_bodies, right_index); - if(left->data == right->data) continue; - - if(collision_check(*left, *right, &collision_left, &collision_right)) { - left->tc->on_collision(left->data, collision_left); - right->tc->on_collision(right->data, collision_right); - } - } - } -} - -static inline -void _internal_physics_apply() { - list_foreach(PhysicsEntity*, entity, &_world_bodies) { - physics_entity_update(*entity); - } -} - -static inline -void _internal_physics_integrate_forces() { - list_foreach(PhysicsEntity*, entity, &_world_bodies) - rigidbody_integrate_forces(entity->tc->get_rigidbody(entity->data)); -} - -void physics_world_tick() { - _internal_physics_integrate_forces(); - _internal_physics_narrow_collision(); - _internal_physics_apply(); -} diff --git a/src/physics_world.h b/src/physics_world.h deleted file mode 100644 index 088b251..0000000 --- a/src/physics_world.h +++ /dev/null @@ -1,14 +0,0 @@ -#ifndef _fencer_physics_world_h -#define _fencer_physics_world_h - -#include "physics_entity.h" - -extern void physics_world_init(); -extern void physics_world_clean(); - -extern void physics_world_add_entity(PhysicsEntity entity); -extern void physics_world_remove_entity(PhysicsEntity entity); - -extern void physics_world_tick(); - -#endif // !_fencer_physics_world_h diff --git a/src/typeclass_helpers.h b/src/typeclass_helpers.h deleted file mode 100644 index 2457295..0000000 --- a/src/typeclass_helpers.h +++ /dev/null @@ -1,7 +0,0 @@ -#ifndef _fencer_typeclass_helpers_h -#define _fencer_typeclass_helpers_h - -#define TC_FN_TYPECHECK(__Return, __Name, ...)\ -__Return (*const __Name##_)(__VA_ARGS__) = __Name; (void)__Name##_ - -#endif // !_fencer_typeclass_helpers_h diff --git a/src/vmath.h b/src/vmath.h deleted file mode 100644 index 3504882..0000000 --- a/src/vmath.h +++ /dev/null @@ -1,164 +0,0 @@ -#ifndef _fencer_vmath_h -#define _fencer_vmath_h - -#include - -#if VMATH_SDL == 1 - -#include -typedef SDL_FPoint Vector; -typedef SDL_Point IVector; - -#else - -typedef struct Vector { - float x; - float y; -} Vector; - -typedef struct IVector { - int x; - int y; -} IVector; - -#endif - -// Vector Constant Macros -#define ZeroVector (Vector){0.0f, 0.0f} -#define InfinityVector (Vector){INFINITY, INFINITY} -#define OneVector (Vector){1.0f,1.0f} - -#define UpVector (Vector){0.0f,-1.0f} -#define RightVector (Vector){1.0f,0.0f} -#define LeftVector (Vector){-1.0f,0.0f} -#define DownVector (Vector){0.0f,1.0f} - -// Integer Vector Constant Macros -#define ZeroIVector (IVector){0,0} -#define OneIVector (IVector){1,1} - -#define UpIVector (IVector){-1,0} -#define DownIVector (IVector){1,0} -#define RightIVector (IVector){1,0} -#define LeftIVector (IVector){-1,0} - -/// -// Floating point vector maths functions. -/// -static inline -int veqf(Vector a, Vector b) { - const float e = 0.0001f; - return fabsf(a.x - b.x) + fabsf(a.y - b.y) < e; -} -static inline -int visnanf(Vector a) { - return isnanf(a.x) || isnanf(a.y); -} -static inline -Vector vaddf(Vector a, Vector b) { - return (Vector){a.x + b.x, a.y + b.y}; -} -static inline -Vector vsubf(Vector a, Vector b) { - return (Vector){a.x - b.x, a.y - b.y}; -} -static inline -Vector vmulff(Vector a, float b) { - return (Vector){a.x * b, a.y * b}; -} -static inline -Vector vmulf(Vector a, Vector b) { - return (Vector) {a.x * b.x, a.y * b.y}; -} -static inline -Vector vinvf(Vector a) { - return (Vector){-a.x, -a.y}; -} -static inline -Vector vperpendicularf(Vector a) { - return (Vector){a.y, -a.x}; -} -static inline -float vmagnitudef(Vector a) { - if(veqf(a, ZeroVector)) return 0.f; - a.x = fabsf(a.x); - a.y = fabsf(a.y); - return sqrtf(a.x*a.x + a.y*a.y); -} -static inline -float vsqrmagnitudef(Vector a) { - a.x = fabsf(a.x); - a.y = fabsf(a.y); - return a.x*a.x + a.y*a.y; -} -static inline -Vector vnormalizedf(Vector a) { - if(veqf(a, ZeroVector)) return ZeroVector; - return vmulff(a, 1.0/vmagnitudef(a)); -} -static inline -float vdotf(Vector a, Vector b) { - return (a.x*b.x) + (a.y*b.y); -} -static inline -float vdistancef(Vector a, Vector b) { - return vmagnitudef(vsubf(a, b)); -} -static inline -float vsqrdistf(Vector a, Vector b) { - return vsqrmagnitudef(vsubf(a, b)); -} -static inline -Vector vreciprocalf(Vector a) { - return (Vector){1.0/a.x, 1.0/a.y}; -} -static inline -Vector vrotatef(Vector a, float t) { - return (Vector){ - cosf(t) * a.x - sinf(t) * a.y, - sinf(t) * a.x + cosf(t) * a.y - }; -} -static inline -float vanglebetweenf(Vector a, Vector b) { - return vdotf(a, b) / (vmagnitudef(a) * vmagnitudef(b)); -} -static inline -Vector vprojectf(Vector onto, Vector from) { - float dot = vdotf(onto, from); - return vmulff(onto, dot); -} -static inline -Vector vlerpf(Vector start, Vector end, float t) { - if(veqf(start, end)) - return end; - t = fminf(fmaxf(0.0f, t), 1.0f); - return vaddf(start, vmulff(vsubf(end, start), t)); -} -static inline -Vector vmovetowardsf(Vector start, Vector end, float delta) { - return vlerpf(start, end, delta / vdistancef(end, start)); -} -static inline -Vector vaveragef(Vector* array, size_t count) { - Vector acc = ZeroVector; - for(size_t i = 0; i < count; ++i) { - acc = vaddf(acc, array[i]); - } - return vmulff(acc, 1.0/(float)count); -} - -static inline -IVector vaddi(IVector a, IVector b) { - return (IVector){a.x + b.x, a.y + b.y}; -} -static inline -IVector vsubi(IVector a, IVector b) { - return (IVector){a.x - b.x, a.y - b.y}; -} -static inline -IVector vmuli(IVector a, IVector b) { - return (IVector){a.x * b.x, a.y * b.y}; -} - -#endif // !_fencer_vmath_h