Modular curves for weapon attributes during lifetime (#6319)

* Partially builds weapon curves.

* Further curve implementation

* Implementation of more output types.

* Cleanup

* Adjusts warning.

* Appeases clang.

* further appeasement

* Appeasement and time-traveling review changes

* change the enum branch too that'd be good

* More requested changes

* Basic abstraction for generic modular curves

* Make curve list by output statically sized

* Make instance-randomization consistent and mapless

* Add parsing

* Add derived modular curve sets

* Fix incorrect unpack

* Split modular curve set and definition, properly comment struct usage

* Split off specific type-traits into own file

* Fix modular curves for multi-derive case

* Implement some basic weapon curves, as well as the damage multiplier hit curve

* Merges in generified system built by Lafiel.

* Fills out beam curvesets.

* Renames curvesets.

* Removes launch curves for now.

* Testing, bugfixing.

* Various cleanup

* Returns extern do_subobj_hit_stuff

* Fixes compile errors

* Beam flag checking

* Rearranges beam flag checking.

---------

Co-authored-by: BMagnu <[email protected]>
This commit is contained in:
KestrelliusandBMagnu authored and GitHub committed 2024-12-10 23:32:46 +01:00
1 parent 25a04fca53
commit 228fd17ff1
16 files changed
+1140 -178

No files matched your search

+19
View File
@@ -0,0 +1,19 @@
#pragma once
#include <optional>
#include <tl/optional.hpp>
template <typename T> struct is_optional : std::false_type {};
template<typename T> struct is_optional<std::optional<T>> : std::true_type {};
template<> struct is_optional<std::nullopt_t> : std::true_type {};
template<typename T> struct is_optional<tl::optional<T>> : std::true_type {};
template<> struct is_optional<tl::nullopt_t> : std::true_type {};
template<typename T>
inline constexpr bool is_optional_v = is_optional<T>::value;
template <typename T> struct is_tuple : std::false_type {};
template <typename... U> struct is_tuple<std::tuple <U...>> : std::true_type {};
template<typename T>
inline constexpr bool is_tuple_v = is_tuple<T>::value;
-2
View File
@@ -221,8 +221,6 @@ int Tool_enabled = 0;
extern int AI_watch_object;
extern int Countermeasures_enabled;
extern float do_subobj_hit_stuff(object *ship_obj, const object *other_obj, const vec3d *hitpos, int submodel_num, float damage, bool *hull_should_apply_armor);
extern void mission_goal_mark_all_true( int type );
int Normal_key_set[] = {
+10 -6
View File
@@ -94,17 +94,21 @@ static void ship_weapon_do_hit_stuff(object *pship_obj, object *weapon_obj, cons
damage = wip->damage;
}
if (wip->damage_incidence_max != 1.0f || wip->damage_incidence_min != 1.0f) {
float dot = -vm_vec_dot(&weapon_obj->orient.vec.fvec, &worldNormal);
float dot = -vm_vec_dot(&weapon_obj->orient.vec.fvec, &worldNormal);
if (wip->damage_incidence_max != 1.0f || wip->damage_incidence_min != 1.0f) {
if (dot < 0.0f)
dot = 0.0f;
damage *= wip->damage_incidence_min + ((wip->damage_incidence_max - wip->damage_incidence_min) * dot);
}
if (wip->damage_curve_idx >= 0)
damage *= Curves[wip->damage_curve_idx].GetValue(f2fl(Missiontime - wp->creation_time) / wip->lifetime);
damage *= wip->weapon_hit_curves.get_output(weapon_info::WeaponHitCurveOutputs::DAMAGE_MULT, std::forward_as_tuple(*wp, *pship_obj, dot), &wp->modular_curves_instance);
// we handle curve scaling for shield damage here, but hull and subsystem damage scaling happens in shiphit.cpp
if (quadrant_num) {
damage *= wip->weapon_hit_curves.get_output(weapon_info::WeaponHitCurveOutputs::SHIELD_DAMAGE_MULT, std::forward_as_tuple(*wp, *pship_obj, dot), &wp->modular_curves_instance);
}
// if this is friendly fire, we check for the friendly fire cap values
if (wp->team == shipp->team) {
@@ -118,7 +122,7 @@ static void ship_weapon_do_hit_stuff(object *pship_obj, object *weapon_obj, cons
}
// deterine whack whack
float blast = wip->mass;
float blast = wip->mass * wip->weapon_hit_curves.get_output(weapon_info::WeaponHitCurveOutputs::MASS_MULT, std::forward_as_tuple(*wp, *pship_obj, dot), &wp->modular_curves_instance);
vm_vec_copy_scale(&force, &weapon_obj->phys_info.vel, blast );
// send player pain packet
@@ -131,7 +135,7 @@ static void ship_weapon_do_hit_stuff(object *pship_obj, object *weapon_obj, cons
}
}
ship_apply_local_damage(pship_obj, weapon_obj, world_hitpos, damage, wip->damage_type_idx, quadrant_num, CREATE_SPARKS, submodel_num);
ship_apply_local_damage(pship_obj, weapon_obj, world_hitpos, damage, wip->damage_type_idx, quadrant_num, CREATE_SPARKS, submodel_num, nullptr, dot);
// let the hud shield gauge know when Player or Player target is hit
hud_shield_quadrant_hit(pship_obj, quadrant_num);
+8 -5
View File
@@ -40,9 +40,11 @@ int collide_weapon_weapon( obj_pair * pair )
if (A->parent_sig == B->parent_sig)
return 1;
float dot = vm_vec_dot(&A->orient.vec.fvec, &B->orient.vec.fvec);
// Only shoot down teammate's missile if not traveling in nearly same direction.
if (Weapons[A->instance].team == Weapons[B->instance].team)
if (vm_vec_dot(&A->orient.vec.fvec, &B->orient.vec.fvec) > 0.7f)
if (dot > 0.7f)
return 1;
// Ignore collisions involving a bomb if the bomb is not yet armed.
@@ -120,15 +122,16 @@ int collide_weapon_weapon( obj_pair * pair )
// damage calculation should not be done on clients, the server will tell the client version of the bomb when to die
if(!a_override && !b_override && !MULTIPLAYER_CLIENT)
{
float dot_curve = -dot;
float aDamage = wipA->damage;
if (wipA->damage_curve_idx >= 0)
aDamage *= Curves[wipA->damage_curve_idx].GetValue(A_time_alive / wipA->lifetime);
aDamage *= wipA->weapon_hit_curves.get_output(weapon_info::WeaponHitCurveOutputs::DAMAGE_MULT, std::forward_as_tuple(*wpA, *B, dot_curve), &wpA->modular_curves_instance);
aDamage *= wipA->weapon_hit_curves.get_output(weapon_info::WeaponHitCurveOutputs::HULL_DAMAGE_MULT, std::forward_as_tuple(*wpA, *B, dot_curve), &wpA->modular_curves_instance);
if (wipB->armor_type_idx >= 0)
aDamage = Armor_types[wipB->armor_type_idx].GetDamage(aDamage, wipA->damage_type_idx, 1.0f, false);
float bDamage = wipB->damage;
if (wipB->damage_curve_idx >= 0)
bDamage *= Curves[wipB->damage_curve_idx].GetValue(B_time_alive / wipB->lifetime);
bDamage *= wipB->weapon_hit_curves.get_output(weapon_info::WeaponHitCurveOutputs::DAMAGE_MULT, std::forward_as_tuple(*wpB, *A, dot_curve), &wpB->modular_curves_instance);
bDamage *= wipB->weapon_hit_curves.get_output(weapon_info::WeaponHitCurveOutputs::HULL_DAMAGE_MULT, std::forward_as_tuple(*wpB, *A, dot_curve), &wpB->modular_curves_instance);
if (wipA->armor_type_idx >= 0)
bDamage = Armor_types[wipA->armor_type_idx].GetDamage(bDamage, wipB->damage_type_idx, 1.0f, false);
+21 -8
View File
@@ -53,6 +53,7 @@
#include "tracing/Monitor.h"
#include "graphics/light.h"
#include "graphics/color.h"
#include "math/curve.h"
extern void ship_reset_disabled_physics(object *objp, int ship_class);
@@ -1252,9 +1253,17 @@ void obj_move_all_post(object *objp, float frametime)
}
}
if (cast_light) {
weapon_info* wi = &Weapon_info[Weapons[objp->instance].weapon_info_index];
weapon* wp = &Weapons[objp->instance];
weapon_info* wi = &Weapon_info[wp->weapon_info_index];
auto lp = lighting_profiles::current();
hdr_color light_color;
float intensity_mult = wi->weapon_curves.get_output(weapon_info::WeaponCurveOutputs::LIGHT_INTENSITY_MULT, *wp, &wp->modular_curves_instance);
float radius_mult = wi->weapon_curves.get_output(weapon_info::WeaponCurveOutputs::LIGHT_RADIUS_MULT, *wp, &wp->modular_curves_instance);
float r_mult = wi->weapon_curves.get_output(weapon_info::WeaponCurveOutputs::LIGHT_R_MULT, *wp, &wp->modular_curves_instance);
float g_mult = wi->weapon_curves.get_output(weapon_info::WeaponCurveOutputs::LIGHT_G_MULT, *wp, &wp->modular_curves_instance);
float b_mult = wi->weapon_curves.get_output(weapon_info::WeaponCurveOutputs::LIGHT_B_MULT, *wp, &wp->modular_curves_instance);
// If there is no specific color set in the table, laser render weapons have a dynamic color.
if (!wi->light_color_set && wi->render_type == WRT_LASER) {
// intensity is stored in the light color even if no user setting is done.
@@ -1263,24 +1272,28 @@ void obj_move_all_post(object *objp, float frametime)
color c;
weapon_get_laser_color(&c, objp);
light_color.set_rgb(&c);
light_color.set_rgb(fl2i(i2fl(light_color.r()) * r_mult), fl2i(i2fl(light_color.g()) * g_mult), fl2i(i2fl(light_color.b()) * b_mult));
light_color.i(light_color.i() * intensity_mult);
} else {
// If not a laser then all default information needed is stored in the weapon light color
light_color = hdr_color(&wi->light_color);
light_color.set_rgb(fl2i(i2fl(light_color.r()) * r_mult), fl2i(i2fl(light_color.g()) * g_mult), fl2i(i2fl(light_color.b()) * b_mult));
}
//handles both defaults and adjustments.
float r = wi->light_radius;
float light_radius = wi->light_radius;
float source_radius = objp->radius;
if (wi->render_type == WRT_LASER) {
r = lp->laser_light_radius.handle(r);
light_color.i(lp->laser_light_brightness.handle(light_color.i()));
light_radius = lp->laser_light_radius.handle(light_radius) * radius_mult;
light_color.i(lp->laser_light_brightness.handle(light_color.i()) * intensity_mult);
} else {
//Missiles should typically not be treated as lights for their whole radius. TODO: make configurable.
source_radius *= 0.05f;
r = lp->missile_light_radius.handle(r);
light_color.i(lp->missile_light_brightness.handle(light_color.i()));
light_radius = lp->missile_light_radius.handle(light_radius) * radius_mult;
light_color.i(lp->missile_light_brightness.handle(light_color.i()) * intensity_mult);
light_color.set_rgb(fl2i(i2fl(light_color.r()) * r_mult), fl2i(i2fl(light_color.g()) * g_mult), fl2i(i2fl(light_color.b()) * b_mult));
}
if(r > 0.0f && light_color.i() > 0.0f)
light_add_point(&objp->pos, r, r, &light_color,source_radius);
if(light_radius > 0.0f && intensity_mult > 0.0f && light_color.i() > 0.0f)
light_add_point(&objp->pos, light_radius, light_radius, &light_color, source_radius);
}
}
+1 -10
View File
@@ -472,14 +472,5 @@ uint32_t hash_fnv1a(const SCP_string& string) {
}
uint32_t hash_fnv1a(const void* data, size_t length) {
const uint32_t fnv1a_magic_prime = 16777619;
uint32_t hash = 2166136261;
auto bytes = reinterpret_cast<const uint8_t*>(data);
for (size_t cnt = 0; cnt < length; cnt++) {
hash = (hash ^ bytes[cnt]) * fnv1a_magic_prime;
}
return hash;
return hash_fnv1a(reinterpret_cast<const uint8_t*>(data), length);
}
+18
View File
@@ -32,8 +32,26 @@ void encrypt(char *text, int text_len, char *scrambled_text, int *scrambled_len,
void unencrypt(char *scrambled_text, int scrambled_len, char *text, int *text_len);
//A fast platform/std-implementation stable hashing algorithm. Implements the FNV-1a hash algorithm
constexpr uint32_t hash_fnv1a(const uint8_t* bytes, size_t length) {
const uint32_t fnv1a_magic_prime = 16777619;
uint32_t hash = 2166136261;
for (size_t cnt = 0; cnt < length; cnt++) {
hash = (hash ^ bytes[cnt]) * fnv1a_magic_prime;
}
return hash;
}
uint32_t hash_fnv1a(const SCP_string& string);
uint32_t hash_fnv1a(const void* data, size_t length);
constexpr uint32_t hash_fnv1a(uint32_t value) {
uint8_t bytes[4] = {static_cast<uint8_t>((value) & 0xff),
static_cast<uint8_t>((value >> 8) & 0xff),
static_cast<uint8_t>((value >> 16) & 0xff),
static_cast<uint8_t>((value >> 24) & 0xff)};
return hash_fnv1a(bytes, sizeof(uint32_t));
}
#endif
+30 -11
View File
@@ -662,7 +662,7 @@ void do_subobj_heal_stuff(const object* ship_objp, const object* other_obj, cons
//
//WMC - hull_should_apply armor means that the initial subsystem had no armor, so the hull should apply armor instead.
float do_subobj_hit_stuff(object *ship_objp, const object *other_obj, const vec3d *hitpos, int submodel_num, float damage, bool *hull_should_apply_armor)
float do_subobj_hit_stuff(object *ship_objp, const object *other_obj, const vec3d *hitpos, int submodel_num, float damage, bool *hull_should_apply_armor, float hit_dot)
{
vec3d g_subobj_pos;
float damage_left, damage_if_hull;
@@ -714,13 +714,32 @@ float do_subobj_hit_stuff(object *ship_objp, const object *other_obj, const vec3
// scale subsystem damage if appropriate
weapon_info_index = shiphit_get_damage_weapon(other_obj); // Goober5000 - a NULL other_obj returns -1
if ((weapon_info_index >= 0) && (other_obj_is_weapon ||
(Beams_use_damage_factors && other_obj_is_beam))) {
if ( Weapon_info[weapon_info_index].wi_flags[Weapon::Info_Flags::Training] ) {
if ((weapon_info_index >= 0) && other_obj_is_weapon) {
weapon* wp = &Weapons[other_obj->instance];
weapon_info* wip = &Weapon_info[weapon_info_index];
if ( wip->wi_flags[Weapon::Info_Flags::Training] ) {
return damage_left;
}
damage_left *= Weapon_info[weapon_info_index].subsystem_factor;
damage_if_hull *= Weapon_info[weapon_info_index].armor_factor;
damage_left *= wip->subsystem_factor;
damage_left *= wip->weapon_hit_curves.get_output(weapon_info::WeaponHitCurveOutputs::SUBSYS_DAMAGE_MULT, std::forward_as_tuple(*wp, *ship_objp, hit_dot), &wp->modular_curves_instance);
damage_if_hull *= wip->armor_factor;
damage_if_hull *= wip->weapon_hit_curves.get_output(weapon_info::WeaponHitCurveOutputs::HULL_DAMAGE_MULT, std::forward_as_tuple(*wp, *ship_objp, hit_dot), &wp->modular_curves_instance);
} else if ((weapon_info_index >= 0) && other_obj_is_beam) {
weapon_info* wip = &Weapon_info[weapon_info_index];
if (Beams_use_damage_factors) {
if ( wip->wi_flags[Weapon::Info_Flags::Training] ) {
return damage_left;
}
damage_left *= wip->subsystem_factor;
damage_if_hull *= wip->armor_factor;
}
beam* b = &Beams[other_obj->instance];
damage_left *= wip->beam_hit_curves.get_output(weapon_info::BeamHitCurveOutputs::SUBSYS_DAMAGE_MULT, std::forward_as_tuple(*b, *other_obj), &b->modular_curves_instance);
damage_if_hull *= wip->beam_hit_curves.get_output(weapon_info::BeamHitCurveOutputs::HULL_DAMAGE_MULT, std::forward_as_tuple(*b, *other_obj), &b->modular_curves_instance);
}
@@ -2272,7 +2291,7 @@ static int maybe_shockwave_damage_adjust(const object *ship_objp, const object *
// Goober5000 - sanity checked this whole function in the case that other_obj is NULL, which
// will happen with the explosion-effect sexp
void ai_update_lethality(const object *ship_objp, const object *weapon_obj, float damage);
static void ship_do_damage(object *ship_objp, object *other_obj, const vec3d *hitpos, float damage, int quadrant, int submodel_num, int damage_type_idx = -1, bool wash_damage = false)
static void ship_do_damage(object *ship_objp, object *other_obj, const vec3d *hitpos, float damage, int quadrant, int submodel_num, int damage_type_idx = -1, bool wash_damage = false, float hit_dot = 1.f)
{
// mprintf(("doing damage\n"));
@@ -2390,7 +2409,7 @@ static void ship_do_damage(object *ship_objp, object *other_obj, const vec3d *hi
}
int weapon_info_index = shiphit_get_damage_weapon(other_obj);
// If we hit the shield, reduce it's strength and found
// out how much damage is left over.
if ( quadrant >= 0 && !(ship_objp->flags[Object::Object_Flags::No_shields]) ) {
@@ -2436,7 +2455,7 @@ static void ship_do_damage(object *ship_objp, object *other_obj, const vec3d *hi
bool apply_hull_armor = true;
// apply damage to subsystems, and get back any remaining damage that needs to go to the hull
damage = do_subobj_hit_stuff(ship_objp, other_obj, hitpos, submodel_num, damage, &apply_hull_armor);
damage = do_subobj_hit_stuff(ship_objp, other_obj, hitpos, submodel_num, damage, &apply_hull_armor, hit_dot);
// damage scaling doesn't apply to subsystems, but it does to the hull
damage *= damage_scale;
@@ -2768,7 +2787,7 @@ void ship_apply_tag(ship *shipp, int tag_level, float tag_time, object *target,
// hitpos is in world coordinates.
// if quadrant is not -1, then that part of the shield takes damage properly.
// (it might be possible to make `other_obj` const, but that would set off another const-cascade)
void ship_apply_local_damage(object *ship_objp, object *other_obj, const vec3d *hitpos, float damage, int damage_type_idx, int quadrant, bool create_spark, int submodel_num, const vec3d *hit_normal)
void ship_apply_local_damage(object *ship_objp, object *other_obj, const vec3d *hitpos, float damage, int damage_type_idx, int quadrant, bool create_spark, int submodel_num, const vec3d *hit_normal, float hit_dot)
{
Assert(ship_objp); // Goober5000
Assert(other_obj); // Goober5000
@@ -2844,7 +2863,7 @@ void ship_apply_local_damage(object *ship_objp, object *other_obj, const vec3d *
create_sparks = false;
}
else
ship_do_damage(ship_objp, other_obj, hitpos, damage, quadrant, submodel_num, damage_type_idx );
ship_do_damage(ship_objp, other_obj, hitpos, damage, quadrant, submodel_num, damage_type_idx, hit_dot);
// DA 5/5/98: move ship_hit_create_sparks() after do_damage() since number of sparks depends on hull strength
// doesn't hit shield and we want sparks
+3 -1
View File
@@ -34,6 +34,8 @@ constexpr float DEATHROLL_ROTVEL_CAP = 6.3f; // maximum added deathroll rotve
// function to destroy a subsystem. Called internally and from multiplayer messaging code
extern void do_subobj_destroyed_stuff( ship *ship_p, ship_subsys *subsys, const vec3d *hitpos, bool no_explosion = false );
float do_subobj_hit_stuff(object *ship_obj, const object *other_obj, const vec3d *hitpos, int submodel_num, float damage, bool *hull_should_apply_armor, float hit_dot = 1.f);
// Goober5000
// (it might be possible to make `target` const, but that would set off another const-cascade)
extern void ship_apply_tag(ship *ship_p, int tag_level, float tag_time, object *target, const vec3d *start, int ssm_index, int ssm_team);
@@ -43,7 +45,7 @@ extern void ship_apply_tag(ship *ship_p, int tag_level, float tag_time, object *
// hitpos is in world coordinates.
// if quadrant is not -1, then that part of the shield takes damage properly.
// (it might be possible to make `other_obj` const, but that would set off another const-cascade)
void ship_apply_local_damage(object *ship_obj, object *other_obj, const vec3d *hitpos, float damage, int damage_type_idx, int quadrant, bool create_spark=true, int submodel_num=-1, const vec3d *hit_normal=nullptr);
void ship_apply_local_damage(object *ship_obj, object *other_obj, const vec3d *hitpos, float damage, int damage_type_idx, int quadrant, bool create_spark=true, int submodel_num=-1, const vec3d *hit_normal=nullptr, float hit_dot = 1.f);
// This gets called to apply damage when a damaging force hits a ship, but at no
// point in particular. Like from a shockwave. This routine will see if the
+2
View File
@@ -400,6 +400,7 @@ add_file_folder("GlobalIncs"
globalincs/systemvars.cpp
globalincs/systemvars.h
globalincs/toolchain.h
globalincs/type_traits.h
globalincs/undosys.cpp
globalincs/undosys.h
globalincs/utility.h
@@ -1668,6 +1669,7 @@ add_file_folder("Utils"
utils/HeapAllocator.h
utils/id.h
utils/join_string.h
utils/modular_curves.h
utils/Random.cpp
utils/Random.h
utils/RandomRange.h
+19 -1
View File
@@ -134,6 +134,13 @@ class RandomRange {
{
return m_maxValue;
}
void seed(typename GeneratorType::result_type new_seed) const {
if (m_constant)
return;
m_generator.seed(new_seed);
}
};
/**
@@ -528,7 +535,15 @@ class ParsedRandomRange {
return range.max();
}
};
struct seed_helper {
unsigned int new_seed;
template <typename T>
inline void operator()(T& range) {
range.seed(new_seed);
}
};
public:
template<typename T>
@@ -548,6 +563,9 @@ class ParsedRandomRange {
inline result_type max() const {
return static_cast<result_type>(mpark::visit(max_helper{}, m_random_range));
}
inline void seed(unsigned int new_seed) const {
mpark::visit(seed_helper{new_seed}, m_random_range);
}
static ParsedRandomRange parseRandomRange(float min = std::numeric_limits<float>::lowest()/2.1f, float max = std::numeric_limits<float>::max()/2.1f) {
switch (optional_string_either("NORMAL", "CURVE")) {
case 0: {
+530
View File
@@ -0,0 +1,530 @@
#pragma once
#include "utils/RandomRange.h"
#include "math/curve.h"
#include "parse/encrypt.h"
#include "globalincs/type_traits.h"
#include "parse/parsehi.h"
#include <optional>
#include <type_traits>
#include <utility>
//
// Modular Curve Input Grabbers
//
// These structs help at building accessors to potential inputs for modular curve definitions
// modular_curves_submember_input -> provides a fully inlineable and compiletime-defined way to access class members, members-of-members, and index into global arrays
// modular_curves_functional_input -> provides an interface for grabbing floats from arbitrary functions. Not as well optimizable as the above, and not declarable inline (until C++20)
//
template<auto... grabbers>
struct modular_curves_submember_input {
private:
template<typename input_type, auto grabber>
static inline auto grab_part(const input_type& input) {
//Pointer to member type, i.e. for submember access
if constexpr (std::is_member_object_pointer_v<decltype(grabber)>) {
if constexpr (std::is_pointer_v<std::remove_reference_t<input_type>>)
return std::cref(input->*grabber);
else
return std::cref(input.*grabber);
}
//Pointer to static variable, i.e. used to index into things.
else if constexpr (std::is_pointer_v<decltype(grabber)>) {
static_assert(std::is_integral_v<std::remove_cv_t<std::remove_reference_t<input_type>>>, "Can only index into array from an integral input");
using indexing_type = std::decay_t<decltype((*grabber)[input])>;
if (input >= 0)
return std::optional<std::reference_wrapper<const indexing_type>>{ std::cref((*grabber)[input]) };
else
return std::optional<std::reference_wrapper<const indexing_type>>(std::nullopt);
}
//Integer, used to index into tuples. Should be rarely used by actual users, but is required to do child-types.
else if constexpr (std::is_integral_v<decltype(grabber)>) {
if constexpr (std::is_pointer_v<std::remove_reference_t<input_type>>)
return std::cref(std::get<grabber>(*input));
else
return std::cref(std::get<grabber>(input));
}
else {
static_assert(!std::is_same_v<input_type, input_type>, "Unknown grabber type in modular curves");
}
}
template<typename input_type, auto grabber, auto... other_grabbers>
static inline auto grab_internal(std::reference_wrapper<const input_type> input) {
//If we're down to one grabber only, return it
if constexpr (sizeof...(other_grabbers) == 0){
return grab_part<input_type, grabber>(input.get());
}
//Otherwise, use the current grabber, and forward the result to the next grabber
else {
const auto& current_access = grab_part<input_type, grabber>(input.get());
//If the current grabber isn't guaranteed to succeed, check for completion first
if constexpr (is_optional_v<std::decay_t<decltype(current_access)>>) {
using lower_return_type = decltype(grab_internal<std::decay_t<decltype((*current_access).get())>, other_grabbers...>(*current_access));
using return_type = typename std::conditional_t<is_optional_v<std::decay_t<lower_return_type>>, lower_return_type, std::optional<lower_return_type>>;
//If we're already nullopt (i.e. this array access failed) return early
if (current_access.has_value()) {
//Now, it's possible that lower acceses _also_ produce optionals. In this case, we need to forward the lower result, not re-wrap it.
if constexpr (is_optional_v<std::decay_t<lower_return_type>>) {
return grab_internal<std::decay_t<decltype((*current_access).get())>, other_grabbers...>(*current_access);
}
else{
return return_type(grab_internal<std::decay_t<decltype((*current_access).get())>, other_grabbers...>(*current_access));
}
}
else
return return_type(std::nullopt);
}
//Otherwise just send it on to the next grabber
else {
return grab_internal<std::decay_t<decltype(current_access.get())>, other_grabbers...>(current_access.get());
}
}
}
template<int tuple_idx, typename input_type>
static inline auto grab_from_tuple(const input_type& input) {
if constexpr(tuple_idx < 0)
return std::cref(input);
else
return std::cref(std::get<tuple_idx>(input));
}
template<typename result_type>
static inline float number_to_float(const result_type& number) {
// if constexpr(std::is_same_v<std::decay_t<result_type>, fix>) // TODO: Make sure we can differentiate fixes from ints.
// return f2fl(number);
// else
if constexpr(std::is_integral_v<std::decay_t<result_type>>)
return static_cast<float>(number);
else if constexpr(std::is_floating_point_v<std::decay_t<result_type>>)
return static_cast<float>(number);
else {
static_assert(!std::is_same_v<result_type, result_type>, "Tried to return non-numeric value");
return 0.f;
}
}
public:
template<int tuple_idx, typename input_type>
static inline float grab(const input_type& input) {
const auto& result = grab_internal<std::decay_t<decltype(grab_from_tuple<tuple_idx, input_type>(input).get())>, grabbers...>(grab_from_tuple<tuple_idx, input_type>(input));
if constexpr (is_optional_v<typename std::decay_t<decltype(result)>>) {
if (result.has_value())
return number_to_float(result->get());
else
return 1.0f;
}
else {
return number_to_float(result.get());
}
}
};
template<auto grabber_fnc>
struct modular_curves_functional_input {
private:
template<int tuple_idx, typename input_type>
static inline auto grab_from_tuple(const input_type& input) {
if constexpr(tuple_idx < 0)
return std::cref(input);
else
return std::cref(std::get<tuple_idx>(input));
}
public:
template<int tuple_idx, typename input_type>
static inline float grab(const input_type& input) {
if constexpr (std::is_pointer_v<std::remove_reference_t<input_type>>){
return grabber_fnc(*grab_from_tuple<tuple_idx, input_type>(input));
}
else {
return grabber_fnc(grab_from_tuple<tuple_idx, input_type>(input));
}
}
};
enum class ModularCurvesMathOperators {
addition,
subtraction,
multiplication,
division,
};
template <typename first, typename second, auto operation>
struct modular_curves_math_input {
public:
template <int tuple_idx, typename input_type>
static inline float grab(const input_type& input) {
float first_value = first::template grab<tuple_idx, input_type>(input);
float second_value = second::template grab<tuple_idx, input_type>(input);
static_assert(std::is_same_v<decltype(operation), ModularCurvesMathOperators>, "Operation type must be in ModularCurvesMathOperators!");
if constexpr (operation == ModularCurvesMathOperators::addition) {
return first_value + second_value;
} else if constexpr (operation == ModularCurvesMathOperators::subtraction) {
return first_value - second_value;
} else if constexpr (operation == ModularCurvesMathOperators::multiplication) {
return first_value * second_value;
} else if constexpr (operation == ModularCurvesMathOperators::division) {
if (second_value != 0.f)
return first_value / second_value;
else
return 1.f;
} else {
static_assert(!std::is_same_v<first, first>, "Unknown operation type");
return 0.f;
}
}
};
struct modular_curves_self_input {
private:
template<int tuple_idx, typename input_type>
static inline auto grab_from_tuple(const input_type& input) {
if constexpr(tuple_idx < 0)
return input;
else
return std::get<tuple_idx>(input);
}
public:
template<int tuple_idx, typename input_type>
static inline float grab(const input_type& input) {
if constexpr (std::is_pointer_v<std::remove_reference_t<input_type>>){
return *grab_from_tuple<tuple_idx, input_type>(input);
}
else {
return grab_from_tuple<tuple_idx, input_type>(input);
}
}
};
//
// Non-template modular curve helper structs, carrying per-curve data as well as data per-instance affected by modular curves.
//
// Do not instantiate this manually. The modular_curve_set struct will manage every use of this struct for you automatically.
struct modular_curves_entry {
int curve_idx = -1;
::util::ParsedRandomFloatRange scaling_factor = ::util::UniformFloatRange(1.f);
::util::ParsedRandomFloatRange translation = ::util::UniformFloatRange(0.f);
bool wraparound = true;
};
//
// This modular_curves_entry_instance contains data for any instances affected by modular curves.
// On the example of Weapon Curves, each weapon instance would have one modular_curves_entry_instance.
// Do not instance this struct manually, instead use modular_curves_set::create_instance()
//
struct modular_curves_entry_instance {
int seed_scaling_factor;
int seed_translation;
};
// Forward declaration of modular_curves_set, see explanation below
template<const auto& definitions, typename input_type, typename output_enum, typename input_tuple_index, typename... input_grabbers>
struct modular_curves_set;
//
// The modular_curves_definition contains the definition of one type of modular curves.
// I.e., something like "Weapon Curves" should have ONE global constexpr instance of a modular_curves_definition struct.
// It is in fact required that this instance is constexpr to be able to instance sets from it.
// Do not instance this by hand, but use the make_modular_curve_definition helper template function,
// or modular_curves_definition::derive_modular_curves_subset for derived defitions.
// output_enum must be a contiguous enum with a NUM_VALUES end
//
template<typename input_type, typename output_enum, size_t output_names, typename input_tuple_index, typename... input_grabbers>
struct modular_curves_definition {
private:
// Friends for instantiation and access from the set.
template<typename, typename, size_t, typename, typename...>
friend struct modular_curves_definition;
template<const auto&, typename, typename, typename, typename...>
friend struct modular_curves_set;
template<const auto&>
friend inline auto make_modular_curve_set();
template<typename, typename output_enum_, size_t output_names_, typename... input_grabbers_>
friend constexpr auto make_modular_curve_definition(std::array<std::pair<const char*, output_enum_>, output_names_> outputs, std::pair<const char*, input_grabbers_>... inputs);
// Internal data
using this_definition_type = modular_curves_definition<input_type, output_enum, output_names, input_tuple_index, input_grabbers...>;
static constexpr size_t num_outputs = static_cast<size_t>(output_enum::NUM_VALUES);
std::array<std::pair<const char*, output_enum>, output_names> outputs;
std::tuple<std::pair<const char*, input_grabbers>...> inputs;
// Constructors and creation helpers
constexpr modular_curves_definition(std::array<std::pair<const char*, output_enum>, output_names> outputs_, std::tuple<std::pair<const char*, input_grabbers>...> inputs_) : outputs(std::move(outputs_)), inputs(std::move(inputs_)) {}
template<const this_definition_type& curve_definition>
static inline auto make_modular_curve_set() {
return modular_curves_set<curve_definition, input_type, output_enum, input_tuple_index, input_grabbers...>{};
}
//Helper function to for parsing
template<bool parsing, size_t... idx>
inline size_t get_input_idx_by_name(const char* input, std::index_sequence<idx...>) const {
size_t result = 0;
bool matched_case = ((!stricmp(input, std::get<idx>(inputs).first) ? (result = idx), true : false) || ...);
if (!matched_case) {
if constexpr (parsing)
error_display(1, "Unexpected modular curve input %s!", input);
else
UNREACHABLE("Unexpected modular curve input %s!", input);
}
return result;
}
// Parsing
void parse(std::array<SCP_vector<std::pair<size_t, modular_curves_entry>>, num_outputs>& curves, const SCP_string& curve_type) const {
while(optional_string(curve_type.c_str())) {
SCP_string input;
required_string("+Input:");
stuff_string(input, F_NAME);
size_t input_idx = get_input_idx_by_name<true>(input.c_str(), std::index_sequence_for<input_grabbers...>{});
SCP_string output;
required_string("+Output:");
stuff_string(output, F_NAME);
bool found_output = false;
auto output_idx = static_cast<output_enum>(0);
for (const auto& output_pair : outputs){
if (!stricmp(output_pair.first, output.c_str())){
found_output = true;
output_idx = output_pair.second;
break;
}
}
if (!found_output){
error_display(1, "Unexpected modular curve output %s!", output.c_str());
}
modular_curves_entry curve_entry;
required_string("+Curve Name:");
SCP_string curve;
stuff_string(curve, F_NAME);
curve_entry.curve_idx = curve_get_by_name(curve);
if (curve_entry.curve_idx < 0){
error_display(1, "Unknown curve %s requested for modular curves!", curve.c_str());
}
if (optional_string("+Random Scaling Factor:")) {
curve_entry.scaling_factor = ::util::ParsedRandomFloatRange::parseRandomRange();
} else {
curve_entry.scaling_factor = ::util::UniformFloatRange(1.0f);
}
if (optional_string("+Random Translation:")) {
curve_entry.translation = ::util::ParsedRandomFloatRange::parseRandomRange();
} else {
curve_entry.translation = ::util::UniformFloatRange(0.0f);
}
curve_entry.wraparound = true;
parse_optional_bool_into("+Wraparound:", &curve_entry.wraparound);
curves[static_cast<std::underlying_type_t<output_enum>>(output_idx)].emplace_back(input_idx, curve_entry);
}
}
void add_curve(std::array<SCP_vector<std::pair<size_t, modular_curves_entry>>, num_outputs>& curves, const SCP_string& input, output_enum output, modular_curves_entry curve_entry) const {
size_t input_idx = get_input_idx_by_name<false>(input.c_str(), std::index_sequence_for<input_grabbers...>{});
curves[static_cast<std::underlying_type_t<output_enum>>(output)].emplace_back(input_idx, curve_entry);
}
// Helper functions to compute the correct type for derived modular curved sets. Meant for unevaluated context (i.e. within a decltype) ONLY!
template<typename maybe_tuple, typename... tuple_additions>
static auto unevaluated_maybe_tuple_cat(const maybe_tuple& in, const tuple_additions&... adds) {
if constexpr(is_tuple_v<maybe_tuple>)
return std::tuple_cat(in, std::tuple<const tuple_additions&...>(adds...));
else
return std::tuple<const maybe_tuple&, const tuple_additions&...>(in, adds...);
}
template<size_t... idx>
static auto unevaluated_tuple_of_input_idx_least_0(std::index_sequence<idx...>) {
return std::tuple<std::integral_constant<int, std::tuple_element_t<idx, input_tuple_index>::value < 0 ? 0 : std::tuple_element_t<idx, input_tuple_index>::value>...>();
}
template<typename tuple_of_integrals, size_t... idx>
static constexpr int find_lowest_tuple_integral_constant(std::index_sequence<idx...>) {
//This explicitly "finds" 0 for a tuple of only -1's
int result = 0;
((result = (result < std::tuple_element_t<idx, tuple_of_integrals>::value ? std::tuple_element_t<idx, tuple_of_integrals>::value : result)), ...);
return result;
}
public:
template<typename additional_input_type, typename new_output_enum, size_t new_output_size, typename... additional_input_grabbers>
constexpr auto derive_modular_curves_subset(std::array<std::pair<const char*, new_output_enum>, new_output_size> new_outputs, std::pair<const char*, additional_input_grabbers>... additional_inputs) const {
using new_input_type = decltype(unevaluated_maybe_tuple_cat(std::declval<input_type>(), std::declval<additional_input_type>()));
using new_input_tuple_index = decltype(std::tuple_cat(
//Old tuple accessors, but if they were -1 (i.e. no tuple) set them to 0 (i.e. first element)
unevaluated_tuple_of_input_idx_least_0(std::index_sequence_for<input_grabbers...>()),
//New tuple accessors, highest observed one + 1
std::tuple<std::integral_constant<std::conditional_t<true, int, additional_input_grabbers>, find_lowest_tuple_integral_constant<input_tuple_index>(std::index_sequence_for<input_grabbers...>()) + 1>...>())); //This "seemingly unnecessary" conditional is required to be able to unpack the parameter pack over the additional_input_grabbers and get a tuple type of the identical length
return modular_curves_definition<
new_input_type,
new_output_enum,
new_output_size,
new_input_tuple_index,
input_grabbers..., additional_input_grabbers...>(
std::move(new_outputs), std::tuple_cat(inputs, std::make_tuple(std::move(additional_inputs)...))
);
}
template<typename additional_input_type, typename... additional_input_grabbers>
constexpr auto derive_modular_curves_input_only_subset(std::pair<const char*, additional_input_grabbers>... additional_inputs) const {
using new_input_type = decltype(unevaluated_maybe_tuple_cat(std::declval<input_type>(), std::declval<additional_input_type>()));
using new_input_tuple_index = decltype(std::tuple_cat(
//Old tuple accessors, but if they were -1 (i.e. no tuple) set them to 0 (i.e. first element)
unevaluated_tuple_of_input_idx_least_0(std::index_sequence_for<input_grabbers...>()),
//New tuple accessors, highest observed one + 1
std::tuple<std::integral_constant<std::conditional_t<true, int, additional_input_grabbers>, find_lowest_tuple_integral_constant<input_tuple_index>(std::index_sequence_for<input_grabbers...>()) + 1>...>())); //This "seemingly unnecessary" conditional is required to be able to unpack the parameter pack over the additional_input_grabbers and get a tuple type of the identical length
return modular_curves_definition<
new_input_type,
output_enum,
0,
new_input_tuple_index,
input_grabbers..., additional_input_grabbers...>(
std::array<std::pair<const char*, output_enum>, 0> {}, std::tuple_cat(inputs, std::make_tuple(std::move(additional_inputs)...))
);
}
};
//
// A modular_curves_set contains all data that is required at runtime to store the parsed table.
// It itself contains no static parsing data, only a compiletime reference to the curve definitions (which have this data)
// On the example of weapon curves, one instance of a modular_curves_set should exist for every weapon class.
// Do not manually instance this struct, instead use the make_modular_curve_set<definition>() helper.
//
template<const auto& definitions, typename input_type, typename output_enum, typename input_tuple_index, typename... input_grabbers>
struct modular_curves_set {
private:
// Friends to allow creation from curve definitions
template<typename, typename, size_t, typename, typename...>
friend struct modular_curves_definition;
// Internal data
static constexpr size_t num_inputs = sizeof...(input_grabbers);
static constexpr size_t num_outputs = static_cast<size_t>(output_enum::NUM_VALUES);
using input_grabber_tuple = std::tuple<input_grabbers...>;
std::array<SCP_vector<std::pair<size_t, modular_curves_entry>>, num_outputs> curves; //Output -> List<(Input, curve_entry)>
constexpr modular_curves_set() : curves() {}
public:
// Used to create an instance for any single thing affected by modular curves. Note that having an instance is purely optional
[[nodiscard]] modular_curves_entry_instance create_instance() const {
return modular_curves_entry_instance{util::Random::next(), util::Random::next()};
}
private:
// Internal methods for computing the curve result
inline std::pair<float, float> get_maybe_instanced_randoms(output_enum output, size_t input, const modular_curves_entry& curve_entry, const modular_curves_entry_instance* instance) const {
if (instance != nullptr) {
static constexpr std::array<std::array<uint32_t, num_outputs>, num_inputs> inout_seeds = []() constexpr {
std::array<std::array<uint32_t, num_outputs>, num_inputs> temp{};
for(uint32_t in = 0; in < static_cast<uint32_t>(num_inputs); in++) {
for(uint32_t out = 0; out < static_cast<uint32_t>(num_outputs); out++) {
//This isn't perfect, but absolutely sufficient to give each input-output combination different random values.
temp[in][out] = hash_fnv1a(hash_fnv1a(in) ^ out);
}
}
return temp;
}();
uint32_t seed = inout_seeds[input][static_cast<std::underlying_type_t<output_enum>>(output)] ^ curve_entry.curve_idx;
curve_entry.scaling_factor.seed(seed ^ instance->seed_scaling_factor);
curve_entry.translation.seed(seed ^ instance->seed_translation);
//This will yield consistent seeds (and thus random values) for the same tuples of input_idx-output_idx-curve_idx-instance_seed.
//if any of these four changes, the resulting value should be random with regard to the previous value.
//Furthermore, this seed generation is not commutative, so input 0 and output 1 will result in a different seed to input 1 and output 0
}
return {curve_entry.scaling_factor.next(), curve_entry.translation.next()};
}
template<size_t... idx>
inline float get_individual_input(size_t input_index, const input_type& input, std::index_sequence<idx...>) const {
float result = 1.f;
//GCC11+ and Clang will properly unroll this fold expression into a switch-case jumptable
bool matched_case = ((idx == input_index ? (result = std::tuple_element_t<idx, input_grabber_tuple>::template grab<std::tuple_element_t<idx, input_tuple_index>::value>(input)), true : false) || ...);
if (!matched_case) {
UNREACHABLE("Modular Curves requested Input %zu which has no grabber!", input_index);
}
return result;
}
public:
bool has_curve(output_enum output) const {
return !curves[static_cast<std::underlying_type_t<output_enum>>(output)].empty();
}
float get_output(output_enum output, const input_type& input, const modular_curves_entry_instance* instance = nullptr) const {
float result = 1.f;
for (const auto& [input_idx, curve_entry] : curves[static_cast<std::underlying_type_t<output_enum>>(output)]){
const auto& [scaling_factor, translation] = get_maybe_instanced_randoms(output, input_idx, curve_entry, instance);
const auto& curve = Curves[curve_entry.curve_idx];
float input_value = (get_individual_input(input_idx, input, std::index_sequence_for<input_grabbers...>{}) / scaling_factor) + translation;
if (curve_entry.wraparound) {
float final_x = curve.keyframes.back().pos.x;
input_value = std::fmod(input_value, final_x);
}
result *= std::max(curve.GetValue(input_value), 0.0f);
}
return result;
}
void reset() {
for (auto& curve_list : curves) {
curve_list.clear();
}
}
inline void parse(const SCP_string& curve_type) {
definitions.parse(curves, curve_type);
}
// Use add_curve to programmatically add a curve as if it had been tabled.
inline void add_curve(const SCP_string& input, output_enum output, modular_curves_entry curve_entry) {
definitions.add_curve(curves, input, output, curve_entry);
}
};
//
// Helper functions to instance the modular curve structs. Helper functions are used because function template argument deduction is slightly more robust than CTAD, resulting in less verbose calls
//
template<typename input_type, typename output_enum, size_t output_names, typename... input_grabbers>
constexpr auto make_modular_curve_definition(std::array<std::pair<const char*, output_enum>, output_names> outputs, std::pair<const char*, input_grabbers>... inputs) {
return modular_curves_definition<
input_type,
output_enum,
output_names,
std::tuple<std::integral_constant<std::conditional_t<true, int, input_grabbers>, -1>...>, //This "seemingly unnecessary" conditional is required to be able to unpack the parameter pack over the input_grabbers and get a tuple type of the identical length
input_grabbers...>(
std::move(outputs), std::make_tuple(std::move(inputs)...)
);
}
template<const auto& curve_definition>
inline auto make_modular_curve_set() {
using modular_curves_definition = std::decay_t<decltype(curve_definition)>;
return modular_curves_definition::template make_modular_curve_set<curve_definition>();
}
#define MODULAR_CURVE_SET(name, source) decltype(make_modular_curve_set<source>()) name = make_modular_curve_set<source>()
+81 -27
View File
@@ -464,6 +464,7 @@ int beam_fire(beam_fire_info *fire_info)
new_item->last_start = fire_info->starting_pos;
new_item->type5_rot_speed = wip->b_info.t5info.continuous_rot;
new_item->rotates = wip->b_info.beam_type == BeamType::OMNI && wip->b_info.t5info.continuous_rot_axis != Type5BeamRotAxis::UNSPECIFIED;
new_item->modular_curves_instance = wip->beam_curves.create_instance();
if (fire_info->bfi_flags & BFIF_FORCE_FIRING)
new_item->flags |= BF_FORCE_FIRING;
@@ -1411,6 +1412,7 @@ void beam_render(beam *b, float u_offset)
float u_scale; // beam tileing -Bobboau
beam_weapon_section_info *bwsi;
beam_weapon_info *bwi;
weapon_info *wip;
memset( h1, 0, sizeof(vertex) * 4 );
@@ -1430,7 +1432,8 @@ void beam_render(beam *b, float u_offset)
//int cull = gr_set_cull(0);
bwi = &Weapon_info[b->weapon_info_index].b_info;
wip = &Weapon_info[b->weapon_info_index];
bwi = &wip->b_info;
// if this beam tracks its own u_offset, use that instead
if (bwi->flags[Weapon::Beam_Info_Flags::Track_own_texture_tiling]) {
@@ -1438,9 +1441,10 @@ void beam_render(beam *b, float u_offset)
b->u_offset_local += flFrametime; // increment *after* we grab the offset so that the first frame will always be at offset=0
}
float alpha_mult = 1.0f;
if (bwi->beam_alpha_curve_idx >= 0)
alpha_mult *= Curves[bwi->beam_alpha_curve_idx].GetValue((b->life_total - b->life_left) / b->life_total);
float width_mult = wip->beam_curves.get_output(weapon_info::BeamCurveOutputs::BEAM_WIDTH_MULT, *b, &b->modular_curves_instance);
float alpha_mult = wip->beam_curves.get_output(weapon_info::BeamCurveOutputs::BEAM_ALPHA_MULT, *b, &b->modular_curves_instance);
bool anim_has_curve = wip->beam_curves.has_curve(weapon_info::BeamCurveOutputs::BEAM_ANIM_STATE);
float anim_state = wip->beam_curves.get_output(weapon_info::BeamCurveOutputs::BEAM_ANIM_STATE, *b, &b->modular_curves_instance);
float length = vm_vec_dist(&b->last_start, &b->last_shot); // beam tileing -Bobboau
float per = 1.0f;
@@ -1465,7 +1469,7 @@ void beam_render(beam *b, float u_offset)
continue;
// calculate the beam points
scale = frand_range(1.0f - bwsi->flicker, 1.0f + bwsi->flicker);
scale = frand_range(1.0f - bwsi->flicker, 1.0f + bwsi->flicker) * width_mult;
beam_calc_facing_pts(&top1, &bottom1, &fvec, &b->last_start, bwsi->width * scale * b->current_width_factor, bwsi->z_add);
beam_calc_facing_pts(&top2, &bottom2, &fvec, &b->last_shot, bwsi->width * scale * scale * b->current_width_factor, bwsi->z_add);
@@ -1510,11 +1514,16 @@ void beam_render(beam *b, float u_offset)
int framenum = 0;
if (bwsi->texture.num_frames > 1) {
b->beam_section_frame[s_idx] += flFrametime;
framenum = bm_get_anim_frame(bwsi->texture.first_frame, b->beam_section_frame[s_idx], bwsi->texture.total_time, true);
if (anim_has_curve) {
framenum = fl2i(i2fl(bwsi->texture.num_frames - 1) * anim_state);
} else {
b->beam_section_frame[s_idx] += flFrametime;
framenum = bm_get_anim_frame(bwsi->texture.first_frame, b->beam_section_frame[s_idx], bwsi->texture.total_time, true);
}
}
CLAMP(framenum, 0, bwsi->texture.num_frames - 1);
float fade = 0.9999f;
if (Neb_affects_beams) {
@@ -1667,14 +1676,22 @@ void beam_render_muzzle_glow(beam *b)
return;
}
bool radius_has_curve = wip->beam_curves.has_curve(weapon_info::BeamCurveOutputs::GLOW_RADIUS_MULT);
float radius_mult = wip->beam_curves.get_output(weapon_info::BeamCurveOutputs::GLOW_RADIUS_MULT, *b, &b->modular_curves_instance);
bool alpha_has_curve = wip->beam_curves.has_curve(weapon_info::BeamCurveOutputs::GLOW_ALPHA_MULT);
float alpha_mult = wip->beam_curves.get_output(weapon_info::BeamCurveOutputs::GLOW_ALPHA_MULT, *b, &b->modular_curves_instance);
bool anim_has_curve = wip->beam_curves.has_curve(weapon_info::BeamCurveOutputs::GLOW_ANIM_STATE);
float anim_state = wip->beam_curves.get_output(weapon_info::BeamCurveOutputs::GLOW_ANIM_STATE, *b, &b->modular_curves_instance);
// if the beam is warming up, scale the glow
if (b->warmup_stamp != -1) {
// get warmup pct
pct = BEAM_WARMUP_PCT(b);
rand_val = 1.0f;
} else
}
// if the beam is warming down
if (b->warmdown_stamp != -1) {
else if (b->warmdown_stamp != -1)
{
// get warmup pct
pct = 1.0f - BEAM_WARMDOWN_PCT(b);
rand_val = 1.0f;
@@ -1685,17 +1702,28 @@ void beam_render_muzzle_glow(beam *b)
rand_val = frand_range(0.90f, 1.0f);
}
rad = wip->b_info.beam_muzzle_radius * pct * rand_val;
if (radius_has_curve) {
rad = wip->b_info.beam_muzzle_radius * radius_mult;
} else {
rad = wip->b_info.beam_muzzle_radius * pct * rand_val;
}
// don't bother trying to draw if there is no radius
if (rad <= 0.0f)
return;
float alpha = get_muzzle_glow_alpha(b);
float alpha;
if (alpha_has_curve) {
alpha = alpha_mult;
} else {
alpha = get_muzzle_glow_alpha(b);
}
if (alpha <= 0.0f)
return;
alpha = MIN(alpha, 1.f);
if (bwi->directional_glow == true){
vertex h1[4];
vertex *verts[4] = { &h1[0], &h1[1], &h1[2], &h1[3] };
@@ -1755,18 +1783,26 @@ void beam_render_muzzle_glow(beam *b)
int framenum = 0;
if ( bwi->beam_glow.num_frames > 1 ) {
b->beam_glow_frame += flFrametime;
framenum = bm_get_anim_frame(bwi->beam_glow.first_frame, b->beam_glow_frame, bwi->beam_glow.total_time, true);
if (anim_has_curve) {
framenum = fl2i(i2fl(bwi->beam_glow.num_frames - 1) * anim_state);
} else {
b->beam_glow_frame += flFrametime;
framenum = bm_get_anim_frame(bwi->beam_glow.first_frame, b->beam_glow_frame, bwi->beam_glow.total_time, true);
}
}
CLAMP(framenum, 0, bwi->beam_glow.num_frames - 1);
//gr_set_bitmap(bwi->beam_glow.first_frame + framenum, GR_ALPHABLEND_FILTER, GR_BITBLT_MODE_NORMAL, alpha * pct);
// draw a poly
//g3_draw_poly(4, verts, TMAP_FLAG_TEXTURED | TMAP_FLAG_CORRECT | TMAP_HTL_3D_UNLIT);
if (!alpha_has_curve) {
alpha = alpha * pct;
}
material material_info;
material_set_unlit_emissive(&material_info, bwi->beam_glow.first_frame + framenum, alpha * pct, 2.0f);
material_set_unlit_emissive(&material_info, bwi->beam_glow.first_frame + framenum, alpha, 2.0f);
g3_render_primitives_textured(&material_info, h1, 4, PRIM_TYPE_TRIFAN, false);
} else {
@@ -1776,18 +1812,26 @@ void beam_render_muzzle_glow(beam *b)
int framenum = 0;
if ( bwi->beam_glow.num_frames > 1 ) {
b->beam_glow_frame += flFrametime;
framenum = bm_get_anim_frame(bwi->beam_glow.first_frame, b->beam_glow_frame, bwi->beam_glow.total_time, true);
if (bwi->beam_glow.num_frames > 1) {
if (anim_has_curve) {
framenum = fl2i(i2fl(bwi->beam_glow.num_frames - 1) * anim_state);
} else {
b->beam_glow_frame += flFrametime;
framenum = bm_get_anim_frame(bwi->beam_glow.first_frame, b->beam_glow_frame, bwi->beam_glow.total_time, true);
}
}
CLAMP(framenum, 0, bwi->beam_glow.num_frames - 1);
//gr_set_bitmap(bwi->beam_glow.first_frame + framenum, GR_ALPHABLEND_FILTER, GR_BITBLT_MODE_NORMAL, alpha * pct);
if (!alpha_has_curve) {
alpha = alpha * pct;
}
// draw 1 bitmap
//g3_draw_bitmap(&pt, 0, rad, tmap_flags);
material mat_params;
material_set_unlit_emissive(&mat_params, bwi->beam_glow.first_frame + framenum, alpha * pct, 2.0f);
material_set_unlit_emissive(&mat_params, bwi->beam_glow.first_frame + framenum, alpha, 2.0f);
g3_render_rect_screen_aligned(&mat_params, &pt, 0, rad, 0.0f);
// maybe draw more
@@ -4032,7 +4076,7 @@ void beam_handle_collisions(beam *b)
{
vec3d force = b->last_shot - b->last_start; // Valathil - use the beam direction as the force direction (like a high pressure water jet)
vm_vec_normalize(&force);
force *= wi->mass;
force *= wi->mass * wi->beam_hit_curves.get_output(weapon_info::BeamHitCurveOutputs::MASS_MULT, std::forward_as_tuple(*b, Objects[target]), &b->modular_curves_instance);
// hit the debris - the debris hit code takes care of checking for MULTIPLAYER_CLIENT, etc
debris_hit(&Objects[target], &Objects[b->objnum], &b->f_collisions[idx].cinfo.hit_point_world, wi->damage, &force);
@@ -4094,7 +4138,7 @@ void beam_handle_collisions(beam *b)
if (!(Game_mode & GM_MULTIPLAYER) || MULTIPLAYER_MASTER) {
vec3d force = b->last_shot - b->last_start; // Valathil - use the beam direction as the force direction (like a high pressure water jet)
vm_vec_normalize(&force);
force *= wi->mass;
force *= wi->mass * wi->beam_hit_curves.get_output(weapon_info::BeamHitCurveOutputs::MASS_MULT, std::forward_as_tuple(*b, Objects[target]), &b->modular_curves_instance);
asteroid_hit(&Objects[target], &Objects[b->objnum], &b->f_collisions[idx].cinfo.hit_point_world, wi->damage, &force);
}
@@ -4273,7 +4317,7 @@ void beam_apply_whack(beam *b, object *objp, vec3d *hit_point)
whack = b_whack_big;
}
}else{
whack = wip->mass;
whack = wip->mass * wip->beam_hit_curves.get_output(weapon_info::BeamHitCurveOutputs::MASS_MULT, std::forward_as_tuple(*b, *objp), &b->modular_curves_instance);
}
// whack direction
@@ -4317,9 +4361,7 @@ float beam_get_ship_damage(beam *b, object *objp, vec3d* hitpos)
}
float damage = wip->damage;
if (wip->damage_curve_idx >= 0)
damage *= Curves[wip->damage_curve_idx].GetValue((b->life_total - b->life_left) / b->life_total);
damage *= wip->beam_hit_curves.get_output(weapon_info::BeamHitCurveOutputs::DAMAGE_MULT, std::forward_as_tuple(*b, *objp), &b->modular_curves_instance);
// same team. yikes
if ( (b->team == Ships[objp->instance].team)
@@ -4377,6 +4419,18 @@ int beam_will_tool_target(beam *b, object *objp)
return (damage_in_a_few_seconds > total_strength);
}
float beam_get_warmup_lifetime_pct(const beam& b) {
return BEAM_WARMUP_PCT((&b));
}
float beam_get_warmdown_lifetime_pct(const beam& b) {
return BEAM_WARMDOWN_PCT((&b));
}
float beam_get_warmdown_age(const beam& b) {
return ((float)timestamp_since(b.warmdown_stamp)) / MILLISECONDS_PER_SECOND;
}
float beam_accuracy = 1.0f;
DCF(b_aim, "Adjusts the beam accuracy factor (Default is 1.0f)")
{
+4 -1
View File
@@ -17,6 +17,7 @@
//
#include "globalincs/globals.h"
#include "model/model.h"
#include "utils/modular_curves.h"
// prototypes
class object;
@@ -218,7 +219,9 @@ typedef struct beam {
bool rotates; // type 5s only, determines whether it rotates
float type5_rot_speed; // how fast it rotates if it does
WeaponState weapon_state;
WeaponState weapon_state;
modular_curves_entry_instance modular_curves_instance;
} beam;
extern std::array<beam, MAX_BEAMS> Beams; // all beams
+196 -10
View File
@@ -33,6 +33,8 @@
#include "model/modelrender.h"
#include "render/3d.h"
#include "utils/modular_curves.h"
class object;
class ship_subsys;
@@ -166,7 +168,7 @@ typedef struct weapon {
float launch_speed; // the initial forward speed (can vary due to additive velocity or acceleration)
// currently only gets set when weapon_info->acceleration_time is used
int next_spawn_time[MAX_SPAWN_TYPES_PER_WEAPON]; // used for continuous child spawn
TIMESTAMP last_spawn_time[MAX_SPAWN_TYPES_PER_WEAPON]; // used for continuous child spawn
mc_info* collisionInfo; // The last collision of this weapon or NULL if it had none
@@ -175,9 +177,12 @@ typedef struct weapon {
WeaponState weapon_state; // The current state of the weapon
float beam_per_shot_rot; // for type 5 beams
modular_curves_entry_instance modular_curves_instance;
} weapon;
// info specific to beam weapons
typedef struct beam_weapon_section_info {
float width; // width of the section
@@ -245,7 +250,6 @@ typedef struct beam_weapon_info {
generic_anim beam_glow; // muzzle glow bitmap
float glow_length; // (DahBlount) determines the length the muzzle glow when using a directional glow
bool directional_glow; // (DahBlount) makes the muzzle glow render to a poly that is oriented along the direction of fire
int beam_alpha_curve_idx; // alpha multiplier over the lifetime of the beam
int beam_shots; // # of shots the beam takes
float beam_shrink_factor; // what percentage of total beam lifetime when the beam starts shrinking
float beam_shrink_pct; // what percent/second the beam shrinks at
@@ -338,6 +342,16 @@ enum class FiringPattern {
RANDOM_REPEATING,
};
float weapon_get_lifetime_pct(const weapon& wp);
float weapon_get_age(const weapon& wp);
float beam_get_warmup_lifetime_pct(const beam& wp);
float beam_get_warmdown_lifetime_pct(const beam& wp);
float beam_get_warmdown_age(const beam& wp);
struct weapon_info;
extern SCP_vector<weapon_info> Weapon_info;
struct weapon_info
{
char name[NAME_LENGTH]; // name of this weapon
@@ -376,19 +390,18 @@ struct weapon_info
float laser_headon_switch_ang; // at what angle
float laser_length;
int laser_length_curve_idx; // length over time curve
bool laser_length_by_frametime;
vec3d bitmap_color; // color modifier for main laser bitmap, unlike the 'laser colors' which affect the glow bitmap
color laser_color_1; // for cycling between glow colors
color laser_color_2; // for cycling between glow colors
float laser_head_radius, laser_tail_radius;
float laser_glow_length_scale;
float laser_glow_head_scale;
float laser_glow_tail_scale;
int laser_radius_curve_idx; // tail + head radius over time curve
float laser_min_pixel_size;
vec3d laser_pos_offset;
float collision_radius_override; // overrides the radius for the purposes of collision
int laser_alpha_curve_idx; // alpha over time curve
bool light_color_set;
hdr_color light_color; //For the light cast by the projectile
@@ -407,7 +420,6 @@ struct weapon_info
float damage; // damage of weapon (for missile, damage within inner radius)
float damage_time; // point in the lifetime of the weapon at which damage starts to attenuate. This applies to non-beam primaries. (DahBlount)
int damage_curve_idx; // damage over time curve
float atten_damage; // The damage to attenuate to. (DahBlount)
float damage_incidence_max; // dmg multipler when weapon hits dead-on (perpindicular)
float damage_incidence_min; // dmg multipler when weapon hits glancing (parallel)
@@ -435,7 +447,6 @@ struct weapon_info
flagset<Weapon::Info_Flags> wi_flags; // bit flags defining behavior, see WIF_xxxx
float turn_time;
int turn_rate_curve_idx; // turn rate over time curve
float turn_accel_time;
float cargo_size; // cargo space taken up by individual weapon (missiles only)
float autoaim_fov; // the weapon specific auto-aim field of view
@@ -664,7 +675,184 @@ struct weapon_info
animation::ModelAnimationSet animations;
public:
enum class WeaponCurveOutputs {
// outputs
LASER_LENGTH_MULT,
LASER_RADIUS_MULT,
LASER_HEAD_RADIUS_MULT,
LASER_TAIL_RADIUS_MULT,
LASER_OFFSET_X_MULT,
LASER_OFFSET_Y_MULT,
LASER_OFFSET_Z_MULT,
LASER_HEADON_SWITCH_ANG_MULT,
LASER_HEADON_SWITCH_RATE_MULT,
LASER_ANIM_STATE,
LASER_ALPHA_MULT,
LASER_BITMAP_R_MULT,
LASER_BITMAP_G_MULT,
LASER_BITMAP_B_MULT,
LASER_GLOW_R_MULT,
LASER_GLOW_G_MULT,
LASER_GLOW_B_MULT,
LIGHT_INTENSITY_MULT,
LIGHT_RADIUS_MULT,
LIGHT_R_MULT,
LIGHT_G_MULT,
LIGHT_B_MULT,
DET_RADIUS_MULT,
TURN_RATE_MULT,
SPAWN_RATE_MULT,
SPAWN_COUNT_MULT,
NUM_VALUES
};
private:
static constexpr auto weapon_modular_curves_definition = make_modular_curve_definition<weapon, WeaponCurveOutputs>(
std::array {
std::pair {"Laser Length Mult", WeaponCurveOutputs::LASER_LENGTH_MULT},
std::pair {"Laser Radius Mult", WeaponCurveOutputs::LASER_RADIUS_MULT},
std::pair {"Laser Head Radius Mult", WeaponCurveOutputs::LASER_HEAD_RADIUS_MULT},
std::pair {"Laser Tail Radius Mult", WeaponCurveOutputs::LASER_TAIL_RADIUS_MULT},
std::pair {"Laser Offset X Mult", WeaponCurveOutputs::LASER_OFFSET_X_MULT},
std::pair {"Laser Offset Y Mult", WeaponCurveOutputs::LASER_OFFSET_Y_MULT},
std::pair {"Laser Offset Z Mult", WeaponCurveOutputs::LASER_OFFSET_Z_MULT},
std::pair {"Laser Headon Transition Angle Mult", WeaponCurveOutputs::LASER_HEADON_SWITCH_ANG_MULT},
std::pair {"Laser Headon Transition Rate Mult", WeaponCurveOutputs::LASER_HEADON_SWITCH_RATE_MULT},
std::pair {"Laser Animation State", WeaponCurveOutputs::LASER_ANIM_STATE},
std::pair {"Laser Alpha Mult", WeaponCurveOutputs::LASER_ALPHA_MULT},
std::pair {"Laser Bitmap R Mult", WeaponCurveOutputs::LASER_BITMAP_R_MULT},
std::pair {"Laser Bitmap G Mult", WeaponCurveOutputs::LASER_BITMAP_G_MULT},
std::pair {"Laser Bitmap B Mult", WeaponCurveOutputs::LASER_BITMAP_B_MULT},
std::pair {"Laser Glow R Mult", WeaponCurveOutputs::LASER_GLOW_R_MULT},
std::pair {"Laser Glow G Mult", WeaponCurveOutputs::LASER_GLOW_G_MULT},
std::pair {"Laser Glow B Mult", WeaponCurveOutputs::LASER_GLOW_B_MULT},
std::pair {"Light Intensity Mult", WeaponCurveOutputs::LIGHT_INTENSITY_MULT},
std::pair {"Light Radius Mult", WeaponCurveOutputs::LIGHT_RADIUS_MULT},
std::pair {"Light R Mult", WeaponCurveOutputs::LIGHT_R_MULT},
std::pair {"Light G Mult", WeaponCurveOutputs::LIGHT_G_MULT},
std::pair {"Light B Mult", WeaponCurveOutputs::LIGHT_B_MULT},
std::pair {"Detonation Radius Mult", WeaponCurveOutputs::DET_RADIUS_MULT},
std::pair {"Turn Rate Mult", WeaponCurveOutputs::TURN_RATE_MULT},
std::pair {"Child Spawn Rate Mult", WeaponCurveOutputs::SPAWN_RATE_MULT},
std::pair {"Child Spawn Count Mult", WeaponCurveOutputs::SPAWN_COUNT_MULT},
},
std::pair {"Lifetime", modular_curves_functional_input<weapon_get_lifetime_pct>{}},
std::pair {"Age", modular_curves_functional_input<weapon_get_age>{}},
std::pair {"Base Velocity", modular_curves_submember_input<&weapon::weapon_max_vel>{}},
std::pair {"Max Hitpoints", modular_curves_submember_input<&weapon::weapon_info_index, &Weapon_info, &weapon_info::weapon_hitpoints>{}},
std::pair {"Current Hitpoints", modular_curves_submember_input<&weapon::objnum, &Objects, &object::hull_strength>{}},
std::pair {"Hitpoints Fraction", modular_curves_math_input<
modular_curves_submember_input<&weapon::objnum, &Objects, &object::hull_strength>,
modular_curves_submember_input<&weapon::weapon_info_index, &Weapon_info, &weapon_info::weapon_hitpoints>,
ModularCurvesMathOperators::division
>{}},
std::pair {"Parent Radius", modular_curves_submember_input<&weapon::objnum, &Objects, &object::parent, &Objects, &object::radius>{}}
);
public:
enum class WeaponHitCurveOutputs {
// outputs
DAMAGE_MULT,
HULL_DAMAGE_MULT,
SHIELD_DAMAGE_MULT,
SUBSYS_DAMAGE_MULT,
MASS_MULT,
NUM_VALUES
};
private:
static constexpr auto weapon_hit_modular_curves_definition = weapon_modular_curves_definition.derive_modular_curves_input_only_subset<object>(
std::pair {"Target Hitpoints", modular_curves_submember_input<&object::hull_strength>{}},
std::pair {"Target Radius", modular_curves_submember_input<&object::radius>{}}
).derive_modular_curves_subset<float, WeaponHitCurveOutputs>(
std::array {
std::pair {"Damage Mult", WeaponHitCurveOutputs::DAMAGE_MULT},
std::pair {"Hull Damage Mult", WeaponHitCurveOutputs::HULL_DAMAGE_MULT},
std::pair {"Shield Damage Mult", WeaponHitCurveOutputs::SHIELD_DAMAGE_MULT},
std::pair {"Subsystem Damage Mult", WeaponHitCurveOutputs::SUBSYS_DAMAGE_MULT},
std::pair {"Mass Mult", WeaponHitCurveOutputs::MASS_MULT},
},
std::pair {"Dot", modular_curves_self_input{}}
);
public:
enum class BeamCurveOutputs {
// outputs
BEAM_WIDTH_MULT,
BEAM_ALPHA_MULT,
BEAM_ANIM_STATE,
GLOW_RADIUS_MULT,
GLOW_ALPHA_MULT,
GLOW_ANIM_STATE,
NUM_VALUES
};
private:
static constexpr auto beam_modular_curves_definition = make_modular_curve_definition<beam, BeamCurveOutputs>(
std::array {
std::pair {"Beam Width Mult", BeamCurveOutputs::BEAM_WIDTH_MULT},
std::pair {"Beam Alpha Mult", BeamCurveOutputs::BEAM_ALPHA_MULT},
std::pair {"Beam Anim State", BeamCurveOutputs::BEAM_ANIM_STATE},
std::pair {"Muzzle Glow Radius Mult", BeamCurveOutputs::GLOW_RADIUS_MULT},
std::pair {"Muzzle Glow Alpha Mult", BeamCurveOutputs::GLOW_ALPHA_MULT},
std::pair {"Muzzle Glow Anim State", BeamCurveOutputs::GLOW_ANIM_STATE},
},
std::pair {"Beam Lifetime", modular_curves_math_input<
modular_curves_math_input<
modular_curves_submember_input<&beam::life_total>,
modular_curves_submember_input<&beam::life_left>,
ModularCurvesMathOperators::subtraction
>,
modular_curves_submember_input<&beam::life_total>,
ModularCurvesMathOperators::division
>{}},
std::pair {"Beam Age", modular_curves_math_input<
modular_curves_submember_input<&beam::life_total>,
modular_curves_submember_input<&beam::life_left>,
ModularCurvesMathOperators::subtraction
>{}},
std::pair {"Warmup Lifetime", modular_curves_functional_input<beam_get_warmup_lifetime_pct>{}},
std::pair {"Warmdown Lifetime", modular_curves_functional_input<beam_get_warmdown_lifetime_pct>{}},
std::pair {"Warmdown Age", modular_curves_functional_input<beam_get_warmdown_age>{}},
std::pair {"Parent Radius", modular_curves_submember_input<&beam::objnum, &Objects, &object::parent, &Objects, &object::radius>{}}
);
public:
enum class BeamHitCurveOutputs {
// outputs
DAMAGE_MULT,
HULL_DAMAGE_MULT,
SHIELD_DAMAGE_MULT,
SUBSYS_DAMAGE_MULT,
MASS_MULT,
NUM_VALUES
};
private:
static constexpr auto beam_hit_modular_curves_definition =
beam_modular_curves_definition
.derive_modular_curves_subset<object, BeamHitCurveOutputs>(
std::array{
std::pair{"Damage Mult", BeamHitCurveOutputs::DAMAGE_MULT},
std::pair{"Hull Damage Mult", BeamHitCurveOutputs::HULL_DAMAGE_MULT},
std::pair{"Shield Damage Mult", BeamHitCurveOutputs::SHIELD_DAMAGE_MULT},
std::pair{"Subsystem Damage Mult", BeamHitCurveOutputs::SUBSYS_DAMAGE_MULT},
std::pair{"Mass Mult", BeamHitCurveOutputs::MASS_MULT},
},
std::pair{"Target Hitpoints", modular_curves_submember_input<&object::hull_strength>{}},
std::pair{"Target Radius", modular_curves_submember_input<&object::radius>{}}
);
public:
MODULAR_CURVE_SET(weapon_curves, weapon_info::weapon_modular_curves_definition);
MODULAR_CURVE_SET(weapon_hit_curves, weapon_info::weapon_hit_modular_curves_definition);
MODULAR_CURVE_SET(beam_curves, weapon_info::beam_modular_curves_definition);
MODULAR_CURVE_SET(beam_hit_curves, weapon_info::beam_hit_modular_curves_definition);
weapon_info();
inline bool is_primary() const { return subtype == WP_LASER || subtype == WP_BEAM; } // either of these is allowed in a primary bank
@@ -731,8 +919,6 @@ public:
extern weapon_explosions Weapon_explosions;
extern SCP_vector<weapon_info> Weapon_info;
extern int Num_weapons;
extern int First_secondary_index;
extern int Default_cmeasure_index;
+198 -96
View File
@@ -1192,6 +1192,10 @@ int parse_weapon(int subtype, bool replace, const char *filename)
stuff_float(&wip->laser_headon_switch_rate);
}
if (optional_string("@Laser Bitmap Color:")) {
stuff_vec3d(&wip->bitmap_color);
}
if(optional_string("@Laser Color:"))
{
// This might be confusing at first glance. If we're a modular table (!first_time),
@@ -1226,12 +1230,14 @@ int parse_weapon(int subtype, bool replace, const char *filename)
stuff_float(&wip->laser_length);
}
if (optional_string("@Multiply Laser Length By Frametime:")) {
stuff_boolean(&wip->laser_length_by_frametime);
}
if (optional_string("@Laser Length Multiplier over Lifetime Curve:")) {
SCP_string curve_name;
stuff_string(curve_name, F_NAME);
wip->laser_length_curve_idx = curve_get_by_name(curve_name);
if (wip->laser_length_curve_idx < 0)
Warning(LOCATION, "Unrecognized laser length curve '%s' for weapon %s", curve_name.c_str(), wip->name);
wip->weapon_curves.add_curve("Lifetime", weapon_info::WeaponCurveOutputs::LASER_LENGTH_MULT, modular_curves_entry{curve_get_by_name(curve_name)});
}
if(optional_string("@Laser Head Radius:")) {
@@ -1245,9 +1251,7 @@ int parse_weapon(int subtype, bool replace, const char *filename)
if (optional_string("@Laser Radius Multiplier over Lifetime Curve:")) {
SCP_string curve_name;
stuff_string(curve_name, F_NAME);
wip->laser_radius_curve_idx = curve_get_by_name(curve_name);
if (wip->laser_radius_curve_idx < 0)
Warning(LOCATION, "Unrecognized laser radius curve '%s' for weapon %s", curve_name.c_str(), wip->name);
wip->weapon_curves.add_curve("Lifetime", weapon_info::WeaponCurveOutputs::LASER_RADIUS_MULT, modular_curves_entry{curve_get_by_name(curve_name)});
}
if (optional_string("@Laser Glow Length Scale:")) {
stuff_float(&wip->laser_glow_length_scale);
@@ -1270,9 +1274,7 @@ int parse_weapon(int subtype, bool replace, const char *filename)
if (optional_string("@Laser Opacity over Lifetime Curve:")) {
SCP_string curve_name;
stuff_string(curve_name, F_NAME);
wip->laser_alpha_curve_idx = curve_get_by_name(curve_name);
if (wip->laser_alpha_curve_idx < 0)
Warning(LOCATION, "Unrecognized laser alpha curve '%s' for weapon %s", curve_name.c_str(), wip->name);
wip->weapon_curves.add_curve("Lifetime", weapon_info::WeaponCurveOutputs::LASER_ALPHA_MULT, modular_curves_entry{curve_get_by_name(curve_name)});
}
if (parse_optional_color3i_into("$Light color:", &wip->light_color)) {
@@ -1359,12 +1361,17 @@ int parse_weapon(int subtype, bool replace, const char *filename)
}
}
std::optional<modular_curves_entry> damage_mult_curve;
if (optional_string("$Damage Multiplier over Lifetime Curve:")) {
//Legacy table. Just populates the modular curve set!
SCP_string curve_name;
stuff_string(curve_name, F_NAME);
wip->damage_curve_idx = curve_get_by_name(curve_name);
if (wip->damage_curve_idx < 0)
int curve = curve_get_by_name(curve_name);
if (curve < 0)
Warning(LOCATION, "Unrecognized damage curve '%s' for weapon %s", curve_name.c_str(), wip->name);
damage_mult_curve.emplace(modular_curves_entry{curve});
}
if(optional_string("$Damage Type:")) {
@@ -1475,6 +1482,14 @@ int parse_weapon(int subtype, bool replace, const char *filename)
}
}
wip->weapon_curves.parse("$Lifetime Curve:");
wip->weapon_hit_curves.parse("$Hit Curve:");
wip->beam_curves.parse("$Beam Curve:");
wip->beam_hit_curves.parse("$Beam Hit Curve:");
if(optional_string("$Energy Consumed:")) {
stuff_float(&wip->energy_consumed);
}
@@ -1588,9 +1603,7 @@ int parse_weapon(int subtype, bool replace, const char *filename)
if (optional_string("+Turn Rate Multiplier over Lifetime Curve:")) {
SCP_string curve_name;
stuff_string(curve_name, F_NAME);
wip->turn_rate_curve_idx = curve_get_by_name(curve_name);
if (wip->turn_rate_curve_idx < 0)
Warning(LOCATION, "Unrecognized turn rate curve '%s' for weapon %s", curve_name.c_str(), wip->name);
wip->weapon_curves.add_curve("Lifetime", weapon_info::WeaponCurveOutputs::TURN_RATE_MULT, modular_curves_entry{curve_get_by_name(curve_name)});
}
if(optional_string("+View Cone:")) {
@@ -1649,9 +1662,7 @@ int parse_weapon(int subtype, bool replace, const char *filename)
if (optional_string("+Turn Rate Multiplier over Lifetime Curve:")) {
SCP_string curve_name;
stuff_string(curve_name, F_NAME);
wip->turn_rate_curve_idx = curve_get_by_name(curve_name);
if (wip->turn_rate_curve_idx < 0)
Warning(LOCATION, "Unrecognized turn rate curve '%s' for weapon %s", curve_name.c_str(), wip->name);
wip->weapon_curves.add_curve("Lifetime", weapon_info::WeaponCurveOutputs::TURN_RATE_MULT, modular_curves_entry{curve_get_by_name(curve_name)});
}
if(optional_string("+View Cone:")) {
@@ -2638,11 +2649,23 @@ int parse_weapon(int subtype, bool replace, const char *filename)
if (spawn_weap < MAX_SPAWN_TYPES_PER_WEAPON)
{
// Get delay out of the way before handling interval so we can do adjusted lifetime
float delay = 0.f;
if (optional_string("+Delay:")) {
stuff_float(&wip->spawn_info[spawn_weap].spawn_interval_delay);
stuff_float(&delay);
// since curves are used for spawn rate now, we just parse the value into a curve of spawn rate over absolute age
// this will behave correctly even if it's being used together with other spawn rate curves
SCP_string curve_name = wip->name;
curve_name += "SpawnDelayCurve" + std::to_string(spawn_weap);
Curve new_curve = Curve(curve_name);
new_curve.keyframes.push_back(curve_keyframe{ vec2d { 0.f, 0.f}, CurveInterpFunction::Constant, 0.0f, 1.0f });
new_curve.keyframes.push_back(curve_keyframe{ vec2d { delay, 1.f}, CurveInterpFunction::Constant, 0.0f, 1.0f });
Curves.push_back(new_curve);
wip->weapon_curves.add_curve("Age", weapon_info::WeaponCurveOutputs::SPAWN_RATE_MULT, modular_curves_entry{(static_cast<int>(Curves.size()) - 1)});
}
float adjusted_lifetime = wip->lifetime - wip->spawn_info[spawn_weap].spawn_interval_delay;
float adjusted_lifetime = wip->lifetime - delay;
if (temp_interval >= MINIMUM_SPAWN_INTERVAL) {
wip->spawn_info[spawn_weap].spawn_interval = temp_interval;
@@ -2798,6 +2821,7 @@ int parse_weapon(int subtype, bool replace, const char *filename)
if ( optional_string("$BeamInfo:") ) {
wip->wi_flags.set(Weapon::Info_Flags::Beam);
// beam type
if(optional_string("+Type:")) {
stuff_string(fname, F_NAME, NAME_LENGTH);
@@ -2905,9 +2929,7 @@ int parse_weapon(int subtype, bool replace, const char *filename)
if (optional_string("+Opacity over Lifetime Curve:")) {
SCP_string curve_name;
stuff_string(curve_name, F_NAME);
wip->b_info.beam_alpha_curve_idx = curve_get_by_name(curve_name);
if (wip->b_info.beam_alpha_curve_idx < 0)
Warning(LOCATION, "Unrecognized beam alpha curve '%s' for weapon %s", curve_name.c_str(), wip->name);
wip->beam_curves.add_curve("Beam Lifetime", weapon_info::BeamCurveOutputs::BEAM_ALPHA_MULT, modular_curves_entry{curve_get_by_name(curve_name)});
}
// # of shots (only used for type D beams)
@@ -3404,6 +3426,13 @@ int parse_weapon(int subtype, bool replace, const char *filename)
}
}
if (damage_mult_curve) {
if (wip->is_beam())
wip->beam_hit_curves.add_curve("Lifetime", weapon_info::BeamHitCurveOutputs::DAMAGE_MULT, *damage_mult_curve);
else
wip->weapon_hit_curves.add_curve("Lifetime", weapon_info::WeaponHitCurveOutputs::DAMAGE_MULT, *damage_mult_curve);
}
while ( optional_string("$Pspew:") ) {
int spew_index = -1;
// check for pspew flag
@@ -5694,8 +5723,8 @@ void weapon_home(object *obj, int num, float frame_time)
vm_vec_copy_scale( &obj->phys_info.desired_vel, &obj->orient.vec.fvec, obj->phys_info.speed);
vec3d turnrate_mod = vm_vec_new(1.0f, 1.0f, 1.0f);
if (wip->turn_rate_curve_idx >= 0)
turnrate_mod *= Curves[wip->turn_rate_curve_idx].GetValue(time_alive / wip->lifetime);
turnrate_mod *= wip->weapon_curves.get_output(weapon_info::WeaponCurveOutputs::TURN_RATE_MULT, *wp, &wp->modular_curves_instance);
// turn the missile towards the target only if non-swarm. Homing swarm missiles choose
// a different vector to turn towards, this is done in swarm_update_direction().
@@ -5798,18 +5827,24 @@ void weapon_process_pre( object *obj, float frame_time)
}
}
float det_radius_adjusted = wip->det_radius;
//WMC - Maybe detonate weapon anyway!
if(wip->det_radius > 0.0f)
if(det_radius_adjusted > 0.0f)
{
det_radius_adjusted *= wip->weapon_curves.get_output(weapon_info::WeaponCurveOutputs::DET_RADIUS_MULT, *wp, &wp->modular_curves_instance);
det_radius_adjusted = MAX(det_radius_adjusted, 0.f);
if((weapon_has_homing_object(wp)) && (wp->homing_object->type != 0))
{
if(!IS_VEC_NULL(&wp->homing_pos) && vm_vec_dist(&wp->homing_pos, &obj->pos) <= wip->det_radius)
if(!IS_VEC_NULL(&wp->homing_pos) && vm_vec_dist(&wp->homing_pos, &obj->pos) <= det_radius_adjusted)
{
weapon_detonate(obj);
}
} else if(wp->target_num > -1)
{
if(vm_vec_dist(&obj->pos, &Objects[wp->target_num].pos) <= wip->det_radius)
if(vm_vec_dist(&obj->pos, &Objects[wp->target_num].pos) <= det_radius_adjusted)
{
weapon_detonate(obj);
}
@@ -5978,22 +6013,27 @@ void weapon_process_post(object * obj, float frame_time)
// do continuous spawns
if (wip->wi_flags[Weapon::Info_Flags::Spawn]) {
for (int i = 0; i < wip->num_spawn_weapons_defined; i++) {
if (wip->spawn_info[i].spawn_interval >= MINIMUM_SPAWN_INTERVAL) {
if (timestamp_elapsed(wp->next_spawn_time[i])) {
spawn_child_weapons(obj, i);
bool spawn = wp->last_spawn_time[i].isNever();
float rate_mult = wip->weapon_curves.get_output(weapon_info::WeaponCurveOutputs::SPAWN_RATE_MULT, *wp, &wp->modular_curves_instance);
if (rate_mult > 0.f) {
int required_elapsed = fl2i((wip->spawn_info[i].spawn_interval / rate_mult) * MILLISECONDS_PER_SECOND);
spawn |= timestamp_elapsed(timestamp_delta(wp->last_spawn_time[i], required_elapsed));
}
// do the spawn effect
if (wip->spawn_info[i].spawn_effect.isValid()) {
auto particleSource = particle::ParticleManager::get()->createSource(wip->spawn_info[i].spawn_effect);
if (spawn) {
spawn_child_weapons(obj, i);
particleSource.moveTo(&obj->pos, &obj->orient);
particleSource.setVelocity(&obj->phys_info.vel);
particleSource.finish();
}
// do the spawn effect
if (wip->spawn_info[i].spawn_effect.isValid()) {
auto particleSource = particle::ParticleManager::get()->createSource(wip->spawn_info[i].spawn_effect);
// update next_spawn_time
wp->next_spawn_time[i] = timestamp() + (int)(wip->spawn_info[i].spawn_interval * 1000.f);
particleSource.moveTo(&obj->pos, &obj->orient);
particleSource.setVelocity(&obj->phys_info.vel);
particleSource.finish();
}
// update next_spawn_time
wp->last_spawn_time[i] = _timestamp();
}
}
}
@@ -6688,6 +6728,8 @@ int weapon_create( const vec3d *pos, const matrix *porient, int weapon_type, int
}
}
wp->modular_curves_instance = wip->weapon_curves.create_instance();
if(wip->wi_flags[Weapon::Info_Flags::Cmeasure]) {
// For the next two frames, any non-timer-based countermeasures will pulse each frame.
Cmeasures_homing_check = 2;
@@ -6850,18 +6892,10 @@ int weapon_create( const vec3d *pos, const matrix *porient, int weapon_type, int
if (wip->wi_flags[Weapon::Info_Flags::Spawn]) {
for (int i = 0; i < wip->num_spawn_weapons_defined; i++) {
if (wip->spawn_info[i].spawn_interval >= MINIMUM_SPAWN_INTERVAL) {
int delay;
if (wip->spawn_info[i].spawn_interval_delay < 0.f)
delay = (int)(wip->spawn_info[i].spawn_interval * 1000);
else
delay = (int)(wip->spawn_info[i].spawn_interval_delay * 1000);
if (delay < 10)
delay = 10; // cap at 10 ms
wp->next_spawn_time[i] = timestamp(delay);
wp->last_spawn_time[i] = TIMESTAMP::never();
}
else
wp->next_spawn_time[i] = INT_MAX; // basically never expire
wp->last_spawn_time[i] = TIMESTAMP::invalid(); // never spawn
}
}
@@ -7023,6 +7057,8 @@ void spawn_child_weapons(object *objp, int spawn_index_override)
spawn_count = wip->spawn_info[i].spawn_count;
}
spawn_count = fl2i(i2fl(spawn_count) * wip->weapon_curves.get_output(weapon_info::WeaponCurveOutputs::SPAWN_COUNT_MULT, *wp, &wp->modular_curves_instance));
child_id = wip->spawn_info[i].spawn_wep_index;
if (child_id < 0)
continue;
@@ -8910,7 +8946,7 @@ void shield_impact_explosion(const vec3d *hitpos, const object *objp, float radi
// renders another laser bitmap on top of the regular bitmap based on the angle of the camera to the front of the laser
// the two are cross-faded into each other so it can switch to the more appropriate bitmap depending on the angle
// returns the alpha multiplier to be used for the main bitmap
float weapon_render_headon_bitmap(object* wep_objp, vec3d* headp, vec3d* tailp, int bitmap, float width1, float width2, int r, int g, int b){
float weapon_render_headon_bitmap(object* wep_objp, vec3d* headp, vec3d* tailp, float switch_ang, float switch_rate, int bitmap, float width1, float width2, int r, int g, int b){
weapon* wp = &Weapons[wep_objp->instance];
weapon_info* wip = &Weapon_info[wp->weapon_info_index];
@@ -8922,18 +8958,18 @@ float weapon_render_headon_bitmap(object* wep_objp, vec3d* headp, vec3d* tailp,
float head_alpha, side_alpha;
// get the head vs side apparent proportions
if (wip->laser_headon_switch_ang < 0.0f) {
if (switch_ang < 0.0f) {
head_alpha = ((width1 + width2) / 2) * fabs(cosf(ang));
side_alpha = wip->laser_length * fabs(sinf(ang));
}
else {
head_alpha = tanf(wip->laser_headon_switch_ang);
head_alpha = tanf(switch_ang);
side_alpha = 1 / head_alpha;
side_alpha = side_alpha * fabs(sinf(ang));
head_alpha = head_alpha * fabs(cosf(ang));
}
head_alpha = powf(head_alpha, wip->laser_headon_switch_rate);
side_alpha = powf(side_alpha, wip->laser_headon_switch_rate);
head_alpha = powf(head_alpha, switch_rate);
side_alpha = powf(side_alpha, switch_rate);
// turn it into 0..1
float head_side_total = head_alpha + side_alpha;
@@ -8941,7 +8977,7 @@ float weapon_render_headon_bitmap(object* wep_objp, vec3d* headp, vec3d* tailp,
side_alpha /= head_side_total;
// make the transition instant past 20
if (wip->laser_headon_switch_rate >= 20.0f) {
if (switch_rate >= 20.0f) {
if (head_alpha > side_alpha) {
head_alpha = 1.0f;
side_alpha = 0.0f;
@@ -8974,6 +9010,7 @@ void weapon_render(object* obj, model_draw_list *scene)
wp = &Weapons[num];
wip = &Weapon_info[Weapons[num].weapon_info_index];
if (wip->wi_flags[Weapon::Info_Flags::Transparent]) {
if (wp->alpha_current == -1.0f) {
wp->alpha_current = wip->alpha_max;
@@ -9000,29 +9037,59 @@ void weapon_render(object* obj, model_draw_list *scene)
{
case WRT_LASER:
{
float length_mult = wip->weapon_curves.get_output(weapon_info::WeaponCurveOutputs::LASER_LENGTH_MULT, *wp, &wp->modular_curves_instance);
float radius_mult = wip->weapon_curves.get_output(weapon_info::WeaponCurveOutputs::LASER_RADIUS_MULT, *wp, &wp->modular_curves_instance);
float head_radius_mult = wip->weapon_curves.get_output(weapon_info::WeaponCurveOutputs::LASER_HEAD_RADIUS_MULT, *wp, &wp->modular_curves_instance);
float tail_radius_mult = wip->weapon_curves.get_output(weapon_info::WeaponCurveOutputs::LASER_TAIL_RADIUS_MULT, *wp, &wp->modular_curves_instance);
float offset_x_mult = wip->weapon_curves.get_output(weapon_info::WeaponCurveOutputs::LASER_OFFSET_X_MULT, *wp, &wp->modular_curves_instance);
float offset_y_mult = wip->weapon_curves.get_output(weapon_info::WeaponCurveOutputs::LASER_OFFSET_Y_MULT, *wp, &wp->modular_curves_instance);
float offset_z_mult = wip->weapon_curves.get_output(weapon_info::WeaponCurveOutputs::LASER_OFFSET_Z_MULT, *wp, &wp->modular_curves_instance);
float switch_ang_mult = wip->weapon_curves.get_output(weapon_info::WeaponCurveOutputs::LASER_HEADON_SWITCH_ANG_MULT, *wp, &wp->modular_curves_instance);
float switch_rate_mult = wip->weapon_curves.get_output(weapon_info::WeaponCurveOutputs::LASER_HEADON_SWITCH_RATE_MULT, *wp, &wp->modular_curves_instance);
bool anim_has_curve = wip->weapon_curves.has_curve(weapon_info::WeaponCurveOutputs::LASER_ANIM_STATE);
float anim_state = wip->weapon_curves.get_output(weapon_info::WeaponCurveOutputs::LASER_ANIM_STATE, *wp, &wp->modular_curves_instance);
float alpha_mult = wip->weapon_curves.get_output(weapon_info::WeaponCurveOutputs::LASER_ALPHA_MULT, *wp, &wp->modular_curves_instance);
float bitmap_r_mult = wip->weapon_curves.get_output(weapon_info::WeaponCurveOutputs::LASER_BITMAP_R_MULT, *wp, &wp->modular_curves_instance);
float bitmap_g_mult = wip->weapon_curves.get_output(weapon_info::WeaponCurveOutputs::LASER_BITMAP_G_MULT, *wp, &wp->modular_curves_instance);
float bitmap_b_mult = wip->weapon_curves.get_output(weapon_info::WeaponCurveOutputs::LASER_BITMAP_B_MULT, *wp, &wp->modular_curves_instance);
float glow_r_mult = wip->weapon_curves.get_output(weapon_info::WeaponCurveOutputs::LASER_GLOW_R_MULT, *wp, &wp->modular_curves_instance);
float glow_g_mult = wip->weapon_curves.get_output(weapon_info::WeaponCurveOutputs::LASER_GLOW_G_MULT, *wp, &wp->modular_curves_instance);
float glow_b_mult = wip->weapon_curves.get_output(weapon_info::WeaponCurveOutputs::LASER_GLOW_B_MULT, *wp, &wp->modular_curves_instance);
if (wip->laser_length_by_frametime) {
length_mult *= flFrametime;
}
float alphaf = 1.0f;
int framenum = 0;
int headon_framenum = 0;
float time = f2fl(Missiontime - wp->creation_time) / wip->lifetime;
float laser_length = wip->laser_length;
if (wip->laser_length_curve_idx >= 0)
laser_length *= Curves[wip->laser_length_curve_idx].GetValue(time);
float radius_mult = 1.0f;
if (wip->laser_radius_curve_idx >= 0) {
radius_mult = Curves[wip->laser_radius_curve_idx].GetValue(time);
if (radius_mult <= 0.0001f)
radius_mult = 0.0001f;
}
laser_length *= length_mult;
float head_radius = wip->laser_head_radius;
float tail_radius = wip->laser_tail_radius;
head_radius *= head_radius_mult * radius_mult;
tail_radius *= tail_radius_mult * radius_mult;
if (laser_length < 0.0001f)
return;
if (head_radius <= 0.0001f) {
head_radius = 0.0001f;
}
if (tail_radius <= 0.0001f) {
tail_radius = 0.0001f;
}
vec3d rotated_offset;
vm_vec_unrotate(&rotated_offset, &wip->laser_pos_offset, &obj->orient);
rotated_offset.xyz.x *= offset_x_mult;
rotated_offset.xyz.y *= offset_y_mult;
rotated_offset.xyz.z *= offset_z_mult;
// By default, the laser's actual position is at its tail point, near the trailing edge of the bitmap.
// Before deriving the headp from the tailp, we move the tailp by the modder-specified offset, multiplied by laser_length.
// That way, the offset is relative to laser_length, and moving the bitmap so that the laser's position is, for example,
@@ -9039,19 +9106,31 @@ void weapon_render(object* obj, model_draw_list *scene)
if (wip->laser_bitmap.num_frames > 1) {
wp->laser_bitmap_frame += flFrametime;
framenum = bm_get_anim_frame(wip->laser_bitmap.first_frame, wp->laser_bitmap_frame, wip->laser_bitmap.total_time, true);
if (anim_has_curve) {
framenum = fl2i(i2fl(wip->laser_bitmap.num_frames - 1) * anim_state);
} else {
framenum = bm_get_anim_frame(wip->laser_bitmap.first_frame, wp->laser_bitmap_frame, wip->laser_bitmap.total_time, true);
}
CLAMP(framenum, 0, wip->laser_bitmap.num_frames - 1);
}
if (wip->laser_headon_bitmap.num_frames > 1) {
wp->laser_headon_bitmap_frame += flFrametime;
headon_framenum = bm_get_anim_frame(wip->laser_headon_bitmap.first_frame, wp->laser_headon_bitmap_frame, wip->laser_headon_bitmap.total_time, true);
if (anim_has_curve) {
framenum = fl2i(i2fl(wip->laser_headon_bitmap.num_frames - 1) * anim_state);
} else {
headon_framenum = bm_get_anim_frame(wip->laser_headon_bitmap.first_frame, wp->laser_headon_bitmap_frame, wip->laser_headon_bitmap.total_time, true);
}
CLAMP(framenum, 0, wip->laser_headon_bitmap.num_frames - 1);
}
if (wip->wi_flags[Weapon::Info_Flags::Transparent])
if (wip->wi_flags[Weapon::Info_Flags::Transparent]) {
alphaf = wp->alpha_current;
if (wip->laser_alpha_curve_idx >= 0)
alphaf *= Curves[wip->laser_alpha_curve_idx].GetValue(time);
}
alphaf *= alpha_mult;
CLAMP(alphaf, 0.0f, 1.0f);
if (Neb_affects_weapons) {
@@ -9062,19 +9141,29 @@ void weapon_render(object* obj, model_draw_list *scene)
// Scale the laser so that it always appears some configured amount of pixels wide, no matter the distance.
// Only affects width, length remains unchanged.
float scaled_head_radius = model_render_get_diameter_clamped_to_min_pixel_size(&headp, wip->laser_head_radius * radius_mult, wip->laser_min_pixel_size);
float scaled_tail_radius = model_render_get_diameter_clamped_to_min_pixel_size(&tailp, wip->laser_tail_radius * radius_mult, wip->laser_min_pixel_size);
float scaled_head_radius = model_render_get_diameter_clamped_to_min_pixel_size(&headp, head_radius, wip->laser_min_pixel_size);
float scaled_tail_radius = model_render_get_diameter_clamped_to_min_pixel_size(&tailp, tail_radius, wip->laser_min_pixel_size);
int alpha = fl2i(alphaf * 255.0f);
int bitmap_r = fl2i(alphaf * wip->bitmap_color.xyz.x * bitmap_r_mult);
int bitmap_g = fl2i(alphaf * wip->bitmap_color.xyz.y * bitmap_g_mult);
int bitmap_b = fl2i(alphaf * wip->bitmap_color.xyz.z * bitmap_b_mult);
CLAMP(bitmap_r, 0, 255);
CLAMP(bitmap_g, 0, 255);
CLAMP(bitmap_b, 0, 255);
// render the head-on bitmap if appropriate and maybe adjust the main bitmap's alpha
if (wip->laser_headon_bitmap.first_frame >= 0) {
main_bitmap_alpha_mult = weapon_render_headon_bitmap(obj, &headp, &tailp,
wip->laser_headon_switch_ang * switch_ang_mult,
wip->laser_headon_switch_rate * switch_rate_mult,
wip->laser_headon_bitmap.first_frame + headon_framenum,
scaled_head_radius,
scaled_tail_radius,
alpha, alpha, alpha);
alpha = fl2i(alphaf * main_bitmap_alpha_mult * 255.0);
bitmap_r, bitmap_g, bitmap_b);
bitmap_r = fl2i(i2fl(bitmap_r) * main_bitmap_alpha_mult);
bitmap_g = fl2i(i2fl(bitmap_g) * main_bitmap_alpha_mult);
bitmap_b = fl2i(i2fl(bitmap_b) * main_bitmap_alpha_mult);
}
batching_add_laser(
@@ -9083,7 +9172,7 @@ void weapon_render(object* obj, model_draw_list *scene)
scaled_head_radius,
&tailp,
scaled_tail_radius,
alpha, alpha, alpha);
bitmap_r, bitmap_g, bitmap_b);
}
// maybe draw laser glow bitmap
@@ -9114,9 +9203,13 @@ void weapon_render(object* obj, model_draw_list *scene)
while (wp->laser_glow_bitmap_frame > wip->laser_glow_bitmap.total_time)
wp->laser_glow_bitmap_frame -= wip->laser_glow_bitmap.total_time;
framenum = fl2i( (wp->laser_glow_bitmap_frame * wip->laser_glow_bitmap.num_frames) / wip->laser_glow_bitmap.total_time );
if (anim_has_curve) {
framenum = fl2i(i2fl(wip->laser_glow_bitmap.num_frames) * anim_state);
} else {
framenum = fl2i( (wp->laser_glow_bitmap_frame * wip->laser_glow_bitmap.num_frames) / wip->laser_glow_bitmap.total_time );
}
CLAMP(framenum, 0, wip->laser_glow_bitmap.num_frames-1);
CLAMP(framenum, 0, wip->laser_glow_bitmap.num_frames - 1);
}
if (wip->laser_glow_headon_bitmap.num_frames > 1) {
@@ -9131,7 +9224,11 @@ void weapon_render(object* obj, model_draw_list *scene)
while (wp->laser_glow_headon_bitmap_frame > wip->laser_glow_headon_bitmap.total_time)
wp->laser_glow_headon_bitmap_frame -= wip->laser_glow_headon_bitmap.total_time;
headon_framenum = fl2i((wp->laser_glow_headon_bitmap_frame * wip->laser_glow_headon_bitmap.num_frames) / wip->laser_glow_headon_bitmap.total_time);
if (anim_has_curve) {
framenum = fl2i(i2fl(wip->laser_glow_headon_bitmap.num_frames) * anim_state);
} else {
headon_framenum = fl2i((wp->laser_glow_headon_bitmap_frame * wip->laser_glow_headon_bitmap.num_frames) / wip->laser_glow_headon_bitmap.total_time);
}
CLAMP(headon_framenum, 0, wip->laser_glow_headon_bitmap.num_frames - 1);
}
@@ -9141,8 +9238,7 @@ void weapon_render(object* obj, model_draw_list *scene)
} else {
alphaf = weapon_glow_alpha;
}
if (wip->laser_alpha_curve_idx >= 0)
alphaf *= Curves[wip->laser_alpha_curve_idx].GetValue(time);
alphaf *= alpha_mult;
CLAMP(alphaf, 0.0f, 1.0f);
if (Neb_affects_weapons) {
@@ -9151,16 +9247,14 @@ void weapon_render(object* obj, model_draw_list *scene)
alphaf *= nebalpha;
}
// Scale the laser so that it always appears some configured amount of pixels wide, no matter the distance.
// Only affects width, length remains unchanged.
float scaled_head_radius = model_render_get_diameter_clamped_to_min_pixel_size(&headp2, wip->laser_head_radius * radius_mult, wip->laser_min_pixel_size);
float scaled_tail_radius = model_render_get_diameter_clamped_to_min_pixel_size(&tailp2, wip->laser_tail_radius * radius_mult, wip->laser_min_pixel_size);
float scaled_head_radius = model_render_get_diameter_clamped_to_min_pixel_size(&headp2, head_radius, wip->laser_min_pixel_size);
float scaled_tail_radius = model_render_get_diameter_clamped_to_min_pixel_size(&tailp2, tail_radius, wip->laser_min_pixel_size);
int r = fl2i(i2fl(c.red) * alphaf);
int g = fl2i(i2fl(c.green) * alphaf);
int b = fl2i(i2fl(c.blue) * alphaf);
int r = fl2i(i2fl(c.red) * glow_r_mult * alphaf);
int g = fl2i(i2fl(c.green) * glow_g_mult * alphaf);
int b = fl2i(i2fl(c.blue) * glow_b_mult * alphaf);
// render the head-on bitmap if appropriate and maybe adjust the main bitmap's alpha
if (wip->laser_glow_headon_bitmap.first_frame >= 0) {
@@ -9177,9 +9271,9 @@ void weapon_render(object* obj, model_draw_list *scene)
&tailp2,
scaled_tail_radius * wip->laser_glow_tail_scale,
r, g, b);
r = fl2i(i2fl(c.red) * alphaf * main_bitmap_alpha_mult);
g = fl2i(i2fl(c.green) * alphaf * main_bitmap_alpha_mult);
b = fl2i(i2fl(c.blue) * alphaf * main_bitmap_alpha_mult);
r = fl2i(i2fl(c.red) * glow_r_mult * alphaf * main_bitmap_alpha_mult);
g = fl2i(i2fl(c.green) * glow_g_mult * alphaf * main_bitmap_alpha_mult);
b = fl2i(i2fl(c.blue) * glow_b_mult * alphaf * main_bitmap_alpha_mult);
}
batching_add_laser(
@@ -9383,7 +9477,8 @@ void weapon_info::reset()
this->laser_headon_switch_ang = -1.0f;
this->laser_headon_switch_rate = 2.0f;
this->laser_length = 10.0f;
this->laser_length_curve_idx = -1;
this->laser_length_by_frametime = false;
this->bitmap_color = { { { 255.f, 255.f, 255.f } } };
gr_init_color(&this->laser_color_1, 255, 255, 255);
gr_init_color(&this->laser_color_2, 255, 255, 255);
this->laser_head_radius = 1.0f;
@@ -9391,15 +9486,17 @@ void weapon_info::reset()
this->laser_glow_length_scale = 2.0f;
this->laser_glow_head_scale = 2.3f;
this->laser_glow_tail_scale = 2.3f;
this->laser_radius_curve_idx = -1;
this->laser_min_pixel_size = Min_pixel_size_laser;
vm_vec_zero(&this->laser_pos_offset);
this->laser_alpha_curve_idx = -1;
this->light_color_set = false;
this->light_color.reset();
this->light_radius = -1.0f; //Defaults handled at runtime via lighting profile if left negative
this->weapon_curves.reset();
this->weapon_hit_curves.reset();
this->beam_curves.reset();
this->collision_radius_override = -1.0f;
this->max_speed = 10.0f;
this->acceleration_time = 0.0f;
@@ -9417,7 +9514,6 @@ void weapon_info::reset()
this->atten_damage = -1.0f;
this->damage_incidence_max = 1.0f;
this->damage_incidence_min = 1.0f;
this->damage_curve_idx = -1;
shockwave_create_info_init(&this->shockwave);
shockwave_create_info_init(&this->dinky_shockwave);
@@ -9446,7 +9542,6 @@ void weapon_info::reset()
this->turn_time = 1.0f;
this->turn_accel_time = 0.f;
this->turn_rate_curve_idx = -1;
this->cargo_size = 1.0f;
this->autoaim_fov = 0.0f;
this->rearm_rate = 1.0f;
@@ -9616,7 +9711,6 @@ void weapon_info::reset()
this->b_info.beam_num_sections = 0;
this->b_info.glow_length = 0;
this->b_info.directional_glow = false;
this->b_info.beam_alpha_curve_idx = -1;
this->b_info.beam_shots = 1;
this->b_info.beam_shrink_factor = 0.0f;
this->b_info.beam_shrink_pct = 0.0f;
@@ -10227,3 +10321,11 @@ bool weapon_multilock_can_lock_on_target(object* shooter, object* target_objp, w
bool weapon_has_homing_object(weapon* wp) {
return wp->homing_object != &obj_used_list;
}
float weapon_get_lifetime_pct(const weapon& wp) {
return f2fl(Missiontime - wp.creation_time) / Weapon_info[wp.weapon_info_index].lifetime;
}
float weapon_get_age(const weapon& wp) {
return f2fl(Missiontime - wp.creation_time);
}