diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 8e680af0..766d837f 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -53,6 +53,8 @@ Version 1.9.0 (not released yet) - Add mod name to main menu - Make value of `spectate_mode_minimal_ui` persist between game launches - Add `version` command +- Add `gore_level` command +- Add gibbing for enemies and corpses at gore level 2 [@is-this-c](https://github.com/is-this-c) - Support `©` in TrueType fonts diff --git a/game_patch/main/main.cpp b/game_patch/main/main.cpp index a164944c..7c9427cd 100644 --- a/game_patch/main/main.cpp +++ b/game_patch/main/main.cpp @@ -44,6 +44,15 @@ GameConfig g_game_config; HMODULE g_hmodule; +std::mt19937 g_rng; + +void initialize_random_generator() +{ + // seed rng with the current time + auto seed = std::chrono::steady_clock::now().time_since_epoch().count(); + g_rng.seed(static_cast(seed)); +} + CallHook rf_init_hook{ 0x004B27CD, []() { diff --git a/game_patch/main/main.h b/game_patch/main/main.h index 2e8ed07e..a6095de5 100644 --- a/game_patch/main/main.h +++ b/game_patch/main/main.h @@ -1,8 +1,14 @@ #pragma once +#include #include extern GameConfig g_game_config; + +// random number generator +extern std::mt19937 g_rng; +void initialize_random_generator(); + #ifdef _WINDOWS_ extern HMODULE g_hmodule; #endif diff --git a/game_patch/multi/server.cpp b/game_patch/multi/server.cpp index 56ce6ca5..f9685ee6 100644 --- a/game_patch/multi/server.cpp +++ b/game_patch/multi/server.cpp @@ -407,6 +407,18 @@ FunHook entity_damage_hook{ } } + // should entity gib? + if (damaged_ep->life < -100.0f && damage_type == 3 && // explosive + damaged_ep->material == 3 && // flesh + rf::game_get_gore_level() >= 2 && + !(damaged_ep->entity_flags & 0x2000000) && // custom_corpse (used by snakes and sea creature) + !(damaged_ep->entity_flags & 0x1) && // dying + !(damaged_ep->entity_flags & 0x1000) && // in_water + !(damaged_ep->entity_flags & 0x2000)) // eye_under_water + { + damaged_ep->entity_flags |= 0x80; + } + float real_damage = entity_damage_hook.call_target(damaged_ep, damage, killer_handle, damage_type, killer_uid); if (rf::is_server && is_pvp_damage && real_damage > 0.0f) { diff --git a/game_patch/object/entity.cpp b/game_patch/object/entity.cpp index c7862762..b51929af 100644 --- a/game_patch/object/entity.cpp +++ b/game_patch/object/entity.cpp @@ -3,8 +3,10 @@ #include #include #include +#include "../os/console.h" #include "../rf/entity.h" #include "../rf/corpse.h" +#include "../rf/multi.h" #include "../rf/weapon.h" #include "../rf/player/player.h" #include "../rf/particle_emitter.h" @@ -201,6 +203,114 @@ CodeInjection entity_process_pre_hide_riot_shield_injection{ }, }; +FunHook entity_blood_throw_gibs_hook{ + 0x0042E3C0, [](int handle) { + rf::Object* objp = rf::obj_from_handle(handle); + + // should only gib on gore level 2 or higher + if (rf::game_get_gore_level() < 2) { + return; + } + + // only gib flesh entities and corpses + if (!objp || (objp->type != rf::OT_ENTITY && objp->type != rf::OT_CORPSE) || objp->material != 3) { + return; + } + + // skip entities with ambient flag (is in original but maybe not necessary?) + rf::Entity* entity = (objp->type == rf::OT_ENTITY) ? static_cast(objp) : nullptr; + if (entity && (entity->info->flags & 0x800000)) { + return; + } + + // skip corpses that shouldn't explode (drools_slime or custom_state_anim) + rf::Corpse* corpse = (objp->type == rf::OT_CORPSE) ? static_cast(objp) : nullptr; + if (corpse && (corpse->corpse_flags & 0x400 || corpse->corpse_flags & 0x4)) { + return; + } + + static constexpr int gib_count = 14; // 7 in original + static constexpr float velocity_scale = 15.0f; + static constexpr float spin_scale_min = 10.0f; + static constexpr float spin_scale_max = 25.0f; + static constexpr int lifetime_ms = 7000; + static constexpr float velocity_factor = 0.5f; + static const char* snd_set = "gib bounce"; + static const std::vector gib_filenames = { + "meatchunk1.v3m", + "meatchunk2.v3m", + "meatchunk3.v3m", + "meatchunk4.v3m", + "meatchunk5.v3m"}; + + for (int i = 0; i < gib_count; ++i) { + rf::DebrisCreateStruct debris_info; + + // random velocity + rf::Vector3 vel; + vel.rand_quick(); + debris_info.vel = vel; + debris_info.vel *= velocity_scale; + debris_info.vel += objp->p_data.vel * velocity_factor; + + // random spin + rf::Vector3 spin; + spin.rand_quick(); + debris_info.spin = spin; + std::uniform_real_distribution range_dist(spin_scale_min, spin_scale_max); + debris_info.spin *= range_dist(g_rng); + + // random orient + rf::Matrix3 orient; + orient.rand_quick(); + debris_info.orient = orient; + + // sound set + rf::ImpactSoundSet* iss = rf::material_find_impact_sound_set(snd_set); + debris_info.iss = iss; + + // other properties + debris_info.pos = objp->pos; + debris_info.lifetime_ms = lifetime_ms; + debris_info.debris_flags = 0x4; + debris_info.obj_flags = 0x8000; // start_hidden + debris_info.material = objp->material; + debris_info.room = objp->room; + + // pick a random gib filename + std::uniform_int_distribution dist(0, gib_filenames.size() - 1); + const char* gib_filename = gib_filenames[dist(g_rng)]; + + rf::Debris* gib = rf::debris_create(objp->handle, gib_filename, 0.3f, &debris_info, 0, -1.0f); + if (gib) { + gib->obj_flags |= rf::OF_INVULNERABLE; + } + } + } +}; + +ConsoleCommand2 gore_level_cmd{ + "gore_level", + [](std::optional gore_setting) { + if (gore_setting) { + if (*gore_setting >= 0 && *gore_setting <= 2) { + rf::game_set_gore_level(*gore_setting); + rf::console::print("Set gore level to {}", rf::game_get_gore_level()); + } + else { + rf::console::print("Invalid gore level specified. Allowed range is 0 (minimal) to 2 (maximum)."); + } + } + else { + rf::console::print("Gore level is {}", rf::game_get_gore_level()); + } + + + }, + "Set gore level.", + "gore_level [level]" +}; + void entity_do_patch() { // Fix player being stuck to ground when jumping, especially when FPS is greater than 200 @@ -252,4 +362,10 @@ void entity_do_patch() // Hide riot shield third person model if entity is hidden (e.g. in cutscenes) entity_process_pre_hide_riot_shield_injection.install(); + + // Restore cut stock game feature for entities and corpses exploding into chunks + entity_blood_throw_gibs_hook.install(); + + // Commands + gore_level_cmd.register_cmd(); } diff --git a/game_patch/rf/geometry.h b/game_patch/rf/geometry.h index e3685ec2..185162f3 100644 --- a/game_patch/rf/geometry.h +++ b/game_patch/rf/geometry.h @@ -6,6 +6,7 @@ #include "os/array.h" #include "os/linklist.h" #include "gr/gr.h" +#include "sound/sound.h" namespace rf { @@ -456,5 +457,7 @@ namespace rf static auto& g_cache_clear = addr_as_ref(0x004F0B90); static auto& g_get_room_render_list = addr_as_ref(0x004D3330); + static auto& material_find_impact_sound_set = addr_as_ref(0x004689A0); + static auto& bbox_intersect = addr_as_ref(0x0046C340); } diff --git a/game_patch/rf/math/matrix.h b/game_patch/rf/math/matrix.h index 7ba9373c..e742a7f7 100644 --- a/game_patch/rf/math/matrix.h +++ b/game_patch/rf/math/matrix.h @@ -24,6 +24,20 @@ namespace rf AddrCaller{0x004FC8A0}.this_call(this, &result); return result; } + + void make_quick(const Vector3& forward_vector) + { + AddrCaller{0x004FCFA0}.this_call(this, &forward_vector); + } + + void rand_quick() + { + Vector3 fvec; + + fvec.rand_quick(); + + make_quick(fvec); + } }; static_assert(sizeof(Matrix3) == 0x24); diff --git a/game_patch/rf/math/vector.h b/game_patch/rf/math/vector.h index 8a9d4e95..3231afcf 100644 --- a/game_patch/rf/math/vector.h +++ b/game_patch/rf/math/vector.h @@ -1,7 +1,10 @@ #pragma once #include +#include #include +#include "../os/os.h" +#include "../../main/main.h" namespace rf { @@ -157,6 +160,22 @@ namespace rf { *this /= len(); } + + void rand_quick() + { + constexpr float TWO_PI = 6.2831855f; + + std::uniform_real_distribution z_dist(-1.0f, 1.0f); + z = z_dist(g_rng); + + std::uniform_real_distribution angle_dist(0.0f, TWO_PI); + float angle = angle_dist(g_rng); + + float scale = std::sqrt(1.0f - z * z); + + x = std::cos(angle) * scale; + y = std::sin(angle) * scale; + } }; static_assert(sizeof(Vector3) == 0xC); diff --git a/game_patch/rf/object.h b/game_patch/rf/object.h index 7e9bd771..95c843bd 100644 --- a/game_patch/rf/object.h +++ b/game_patch/rf/object.h @@ -6,6 +6,7 @@ #include "math/matrix.h" #include "os/array.h" #include "os/string.h" +#include "sound/sound.h" #include "physics.h" #include "vmesh.h" @@ -36,9 +37,10 @@ namespace rf OT_GLARE = 0xA, }; - enum ObjectFlags + enum ObjectFlags : int { OF_DELAYED_DELETE = 0x2, + OF_INVULNERABLE = 0x4, OF_WAS_RENDERED = 0x10, OF_IN_LIQUID = 0x80000, OF_HAS_ALPHA = 0x100000, @@ -121,6 +123,37 @@ namespace rf }; static_assert(sizeof(ObjectCreateInfo) == 0x98); + struct Debris : Object + { + Debris* next; + Debris* prev; + void* solid; + int vmesh_submesh; + int debris_flags; + int explosion_index; + Timestamp lifetime; + int frame_num; + int sound; + String* custom_sound_set; + }; + static_assert(sizeof(Debris) == 0x2B4); + + struct DebrisCreateStruct + { + Vector3 pos; + Matrix3 orient; + Vector3 vel; + Vector3 spin; + int lifetime_ms; + int material; + int explosion_index; + int debris_flags; + int obj_flags; + void* room; + ImpactSoundSet* iss; + }; + static_assert(sizeof(DebrisCreateStruct) == 0x64); + static auto& obj_lookup_from_uid = addr_as_ref(0x0048A4A0); static auto& obj_from_handle = addr_as_ref(0x0040A0E0); static auto& obj_flag_dead = addr_as_ref(0x0048AB40); @@ -134,6 +167,9 @@ namespace rf static auto& obj_light_calculate = addr_as_ref(0x0048B0E0); static auto& object_list = addr_as_ref(0x0073D880); + + static auto& debris_create = addr_as_ref(0x00412E70); } template<> diff --git a/game_patch/rf/player/player.h b/game_patch/rf/player/player.h index 7cd581b4..fa966a94 100644 --- a/game_patch/rf/player/player.h +++ b/game_patch/rf/player/player.h @@ -173,4 +173,6 @@ namespace rf static auto& player_do_frame = addr_as_ref(0x004A2700); static auto& player_make_weapon_current_selection = addr_as_ref(0x004A4980); static auto& player_start_death_fade = addr_as_ref(0x004A73E0); + static auto& game_get_gore_level = addr_as_ref(0x00436A20); + static auto& game_set_gore_level = addr_as_ref(0x00436A10); } diff --git a/game_patch/rf/sound/sound.h b/game_patch/rf/sound/sound.h index 750a2732..0539c141 100644 --- a/game_patch/rf/sound/sound.h +++ b/game_patch/rf/sound/sound.h @@ -4,6 +4,7 @@ #include "../math/vector.h" #include "../math/matrix.h" #include "../os/timestamp.h" +#include "../os/string.h" namespace rf { @@ -54,6 +55,15 @@ namespace rf }; static_assert(sizeof(AmbientSound) == 0x18); + struct ImpactSoundSet + { + int sounds[40]; + int num_material_sounds[10]; + int is_all_sounds; + String name; + }; + static_assert(sizeof(ImpactSoundSet) == 0xD4); + constexpr int SOUND_GROUP_EFFECTS = 0; constexpr int SOUND_GROUP_MUSIC = 1; constexpr int SOUND_GROUP_VOICE_MESSAGES = 2; diff --git a/game_patch/rf/weapon.h b/game_patch/rf/weapon.h index 16d7cddc..58143456 100644 --- a/game_patch/rf/weapon.h +++ b/game_patch/rf/weapon.h @@ -3,11 +3,10 @@ #include #include "object.h" #include "os/timestamp.h" +#include "sound/sound.h" namespace rf { - struct ImpactSoundSet; - struct WeaponStateAction { String name; diff --git a/resources/CMakeLists.txt b/resources/CMakeLists.txt index 66badfe8..29d77496 100644 --- a/resources/CMakeLists.txt +++ b/resources/CMakeLists.txt @@ -44,6 +44,7 @@ add_packfile(dashfaction.vpp images/bullet_icon_powercell_1.tga images/bullet_icon_rocket_1.tga images/bullet_icon_shotgun_1.tga + images/gore1.tga images/enviro0_1.tga images/enviro10_1.tga images/enviro20_1.tga @@ -87,6 +88,11 @@ add_packfile(dashfaction.vpp meshes/Vat1.v3m meshes/coffeesmokedtbl2.v3m meshes/coffeesmokedtblAlt.v3m + meshes/meatchunk1.v3m + meshes/meatchunk2.v3m + meshes/meatchunk3.v3m + meshes/meatchunk4.v3m + meshes/meatchunk5.v3m standard_vs:${CMAKE_BINARY_DIR}/shaders/standard_vs.bin character_vs:${CMAKE_BINARY_DIR}/shaders/character_vs.bin diff --git a/resources/images/gore1.tga b/resources/images/gore1.tga new file mode 100644 index 00000000..aac3bcdb Binary files /dev/null and b/resources/images/gore1.tga differ diff --git a/resources/licensing-info.txt b/resources/licensing-info.txt index 1274e733..e488ed7c 100644 --- a/resources/licensing-info.txt +++ b/resources/licensing-info.txt @@ -27,6 +27,7 @@ Chris "Goober" Parsons: * HUD textures for the big mode * symbol glyphs in ttf fonts * launcher header +* texture for gibs natalie : * code contributions diff --git a/resources/maps_df.txt b/resources/maps_df.txt index df567b33..3608bfb9 100644 --- a/resources/maps_df.txt +++ b/resources/maps_df.txt @@ -450,3 +450,4 @@ data\maps\textures\damage\OilDrum_Debris.tga data\maps\textures\damage\genericdebris01.tga data\maps\textures\damage\offtablenew01_debris.tga data\maps\textures\trim\damage.tga +data\maps\textures\gore\gore1.tga diff --git a/resources/meshes/meatchunk1.v3m b/resources/meshes/meatchunk1.v3m new file mode 100644 index 00000000..8503c7d0 Binary files /dev/null and b/resources/meshes/meatchunk1.v3m differ diff --git a/resources/meshes/meatchunk2.v3m b/resources/meshes/meatchunk2.v3m new file mode 100644 index 00000000..fc07c74f Binary files /dev/null and b/resources/meshes/meatchunk2.v3m differ diff --git a/resources/meshes/meatchunk3.v3m b/resources/meshes/meatchunk3.v3m new file mode 100644 index 00000000..8acdc8fc Binary files /dev/null and b/resources/meshes/meatchunk3.v3m differ diff --git a/resources/meshes/meatchunk4.v3m b/resources/meshes/meatchunk4.v3m new file mode 100644 index 00000000..b8e4d4d8 Binary files /dev/null and b/resources/meshes/meatchunk4.v3m differ diff --git a/resources/meshes/meatchunk5.v3m b/resources/meshes/meatchunk5.v3m new file mode 100644 index 00000000..044c8a40 Binary files /dev/null and b/resources/meshes/meatchunk5.v3m differ