mirror of
https://github.com/scp-fs2open/fs2open.github.com.git
synced 2026-11-07 08:15:59 -10:00
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:
1 parent
25a04fca53
commit
228fd17ff1
16 files changed
+1140
-178
No files matched your search
@@ -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;
|
||||
@@ -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[] = {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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
@@ -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);
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
}
|
||||
Reference in new issue
Block a user