Overheating - Add cook off and rate of fire features and additional customization settings (#8064)

* Add jamming coef to change or disable jamming.

* change max to 5

* add setting for overheating effects distance, unjaming on barrel swap, increase rate of fire with heat

- add setting for overheating effects distance
- add unjaming on barrel swap, with setting
- add increase rate of fire with heat, with setting
- fix some formatting

* little tweaks

* add overheating cookoff feature

- add overheating cookoff feature
- add documentation
- bugfixes/improvements

* Update ace3-config-entries.md

* Update overheating-framework.md

* Update addons/overheating/XEH_postInit.sqf

Co-authored-by: jonpas <jonpas33@gmail.com>

* Update addons/overheating/XEH_postInit.sqf

Co-authored-by: jonpas <jonpas33@gmail.com>

* Update addons/overheating/functions/fnc_firedEH.sqf

Co-authored-by: jonpas <jonpas33@gmail.com>

* Update addons/overheating/stringtable.xml

Co-authored-by: jonpas <jonpas33@gmail.com>

* Update docs/wiki/feature/overheating.md

Co-authored-by: jonpas <jonpas33@gmail.com>

* Update addons/overheating/stringtable.xml

Co-authored-by: jonpas <jonpas33@gmail.com>

* Update addons/overheating/functions/fnc_jamWeapon.sqf

Co-authored-by: jonpas <jonpas33@gmail.com>

* Update addons/overheating/functions/fnc_jamWeapon.sqf

Co-authored-by: jonpas <jonpas33@gmail.com>

* remove extra underwater cooling, make cookoffCoef enable cookoff

- add coef setting for heat generation per shot
- merge cookoff setting into cookoff coef setting
- remove check for water that increased cooling
- change max rof increase from heat to 10%
- change ammo heating to a less linear formula
- change cookoffCoef to effect inginition tempurature instead of heat amount
- delay cookoff shot until any firing animation is done
- update strings based on feedback

* Update stringtable.xml

* add cookoff notification

* improvements from play testing

- move ammo heat loop into seperate function with a tighter loop
- factor rain into cooling calculation
- handle cooling while swimming
- merge cookoff take event handler into fnc_handleTakeEH
- fix case where cookoff could potentially come from underbarrel weapon muzzle
- only add TakeEH if required by enabled settings
- improve cookoff muzzle/mode handling

* fix missing semi that I swear I already fixed before pushing

* Update overheating-framework.md

* Update fnc_updateAmmoTemperature.sqf

* include wind speed in cooling calculation

* cool with X

- add ace interactions to allow cooling with water sources when Ace X is loaded
- add documentation for cooling
- move getting barrel mass to a function

* documentation formatting

* Add config array for weapon jam types, as not all weapon can get all types IRL.

* remove variable that's not required

* add some compat entries for RHS

* fix merge conflict

* fix a happy little accident

* move to CBA settings, minor styling.

* Update error message in fnc_jamWeapon.sqf

Co-authored-by: jonpas <jonpas33@gmail.com>

* Apply suggestions from code review

Co-authored-by: TyroneMF <TyroneMF@hotmail.com>

Co-authored-by: jonpas <jonpas33@gmail.com>
Co-authored-by: TyroneMF <TyroneMF@hotmail.com>
This commit is contained in:
Drofseh 2021-10-14 08:47:52 -07:00 committed by GitHub
parent b06d6fc0f8
commit f83c605958
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 957 additions and 104 deletions

View File

@ -2,6 +2,9 @@ class ACE_Settings {
class GVAR(enabled) {
movedToSQF = 1;
};
class GVAR(heatCoef) {
movedToSQF = 1;
};
class GVAR(showParticleEffects) {
movedToSQF = 1;
};
@ -11,13 +14,28 @@ class ACE_Settings {
class GVAR(overheatingDispersion) {
movedToSQF = 1;
};
class GVAR(particleEffectsAndDispersionDistance) {
movedToSQF = 1;
};
class GVAR(overheatingRateOfFire) {
movedToSQF = 1;
};
class GVAR(displayTextOnJam) {
movedToSQF = 1;
};
class GVAR(unJamOnreload) {
class GVAR(jamChanceCoef) {
movedToSQF = 1;
};
class GVAR(unJamOnReload) {
movedToSQF = 1;
};
class GVAR(unJamOnSwapBarrel) {
movedToSQF = 1;
};
class GVAR(unJamFailChance) {
movedToSQF = 1;
};
class GVAR(cookoffCoef) {
movedToSQF = 1;
};
};

View File

@ -23,4 +23,26 @@ class CfgSounds {
sound[]={QPATHTOF(sounds\fixing_pistol.wav),1,1};
titles[]={};
};
/* // to be added when licence compatible audio can be found or recorded
class GVAR(pouring_long) {
name= QGVAR(pouring_long);
sound[]={QPATHTOF(sounds\pouring_long.ogg),5,1};
titles[]={};
};
class GVAR(pouring_short) {
name= QGVAR(pouring_short);
sound[]={QPATHTOF(sounds\pouring_short.ogg),5,1};
titles[]={};
};
class GVAR(sizzling_long) {
name= QGVAR(sizzling_long);
sound[]={QPATHTOF(sounds\sizzling_long.ogg),5,1};
titles[]={};
};
class GVAR(sizzling_short) {
name= QGVAR(sizzling_short);
sound[]={QPATHTOF(sounds\sizzling_short.ogg),5,1};
titles[]={};
};
*/
};

View File

@ -36,6 +36,15 @@ class CfgVehicles {
showDisabled = 0;
icon = QUOTE(PATHTOF(UI\temp_ca.paa));
};
class GVAR(CoolWeaponWithItem) {
displayName = CSTRING(CoolWeaponWithItem);
condition = QUOTE(GVAR(enabled) && {isClass(configfile >> 'CfgPatches' >> 'acex_field_rations')});
exceptions[] = {"isNotInside", "isNotSwimming", "isNotSitting"};
statement = "true";
showDisabled = 0;
insertChildren = QUOTE(_player call FUNC(getConsumableChildren));
icon = QPATHTOF(UI\pour_water_ca.paa);
};
};
};
@ -55,6 +64,15 @@ class CfgVehicles {
statement = QUOTE( [ARR_3(_player, _target, currentWeapon _target)] call FUNC(checkTemperature); );
icon = QUOTE(PATHTOF(UI\temp_ca.paa));
};
class GVAR(CoolWeaponWithItem) {
displayName = CSTRING(CoolWeaponWithItem);
condition = QUOTE(GVAR(enabled) && {isClass(configfile >> 'CfgPatches' >> 'acex_field_rations')});
exceptions[] = {"isNotInside", "isNotSwimming", "isNotSitting"};
statement = "true";
showDisabled = 0;
insertChildren = QUOTE(_player call FUNC(getConsumableChildren));
icon = QPATHTOF(UI\pour_water_ca.paa);
};
};
};
};

View File

@ -1,4 +1,19 @@
class CfgWeapons {
class PistolCore;
class Pistol : PistolCore {
//Closed Bolt (Closed Bolt will cook off if too hot)
//Pistols are nearly universally closed bolt.
GVAR(closedBolt) = 1;
};
class Pistol_Base_F : Pistol {};
class hgun_Pistol_heavy_02_F : Pistol_Base_F {
GVAR(jamTypesAllowed) = ["Fire","Dud"];
};
class hgun_Pistol_Signal_F : Pistol_Base_F {
GVAR(jamTypesAllowed) = ["Fire","Dud"];
};
class RifleCore;
class Rifle: RifleCore {
//Mean Rounds Between Stoppages (this will be scaled based on the barrel temp)
@ -9,10 +24,17 @@ class CfgWeapons {
//Slowdown Factor (this will be scaled based on the barrel temp)
GVAR(slowdownFactor) = 1;
//Closed Bolt, most weapons are closed bolt
GVAR(closedBolt) = 1;
};
class Rifle_Base_F;
class Rifle_Long_Base_F: Rifle_Base_F {
GVAR(dispersion) = 0.75;
// Open Bolt, most machine guns are open bolt, which cannot normally cook off, and use this as a parent class
// A lot of sniper rifles also use this as a parent class, they will need to be indivisually set to closed bolt, but it's probably not an issue as they are unlikely to overheat
GVAR(closedBolt) = 0;
};
class arifle_MX_Base_F: Rifle_Base_F {
@ -26,6 +48,36 @@ class CfgWeapons {
GVAR(allowSwapBarrel) = 1;
GVAR(dispersion) = 0.75;
};
class DMR_01_base_F : Rifle_Long_Base_F {
GVAR(closedBolt) = 1;
};
class DMR_02_base_F : Rifle_Long_Base_F {
GVAR(closedBolt) = 1;
};
class DMR_03_base_F : Rifle_Long_Base_F {
GVAR(closedBolt) = 1;
};
class DMR_04_base_F : Rifle_Long_Base_F {
GVAR(closedBolt) = 1;
};
class DMR_05_base_F : Rifle_Long_Base_F {
GVAR(closedBolt) = 1;
};
class DMR_06_base_F : Rifle_Long_Base_F {
GVAR(closedBolt) = 1;
};
class DMR_07_base_F : Rifle_Long_Base_F {
GVAR(closedBolt) = 1;
};
class EBR_base_F : Rifle_Long_Base_F {
GVAR(closedBolt) = 1;
};
class GM6_base_F : Rifle_Long_Base_F {
GVAR(closedBolt) = 1;
};
class LRR_base_F : Rifle_Long_Base_F {
GVAR(closedBolt) = 1;
};
class MMG_01_base_F: Rifle_Long_Base_F {
GVAR(allowSwapBarrel) = 1;
};
@ -41,6 +93,10 @@ class CfgWeapons {
class LMG_03_Base_F: Rifle_Long_Base_F {
GVAR(allowSwapBarrel) = 1;
};
class sgun_HunterShotgun_01_base_F : Rifle_Long_Base_F {
GVAR(closedBolt) = 1;
GVAR(jamTypesAllowed) = ["Fire","Dud"];
};
class ACE_ItemCore;
class CBA_MiscItem_ItemInfo;
class ACE_SpareBarrel_Item: ACE_ItemCore {

Binary file not shown.

View File

@ -6,8 +6,12 @@ PREP(canCheckSpareBarrelsTemperatures);
PREP(checkSpareBarrelsTemperatures);
PREP(checkTemperature);
PREP(clearJam);
PREP(coolWeaponWithItem);
PREP(coolWeaponWithWaterSource);
PREP(displayTemperature);
PREP(firedEH);
PREP(getBarrelMass);
PREP(getConsumableChildren);
PREP(getWeaponData);
PREP(handleTakeEH);
PREP(handleRespawn);
@ -18,6 +22,8 @@ PREP(sendSpareBarrelsTemperaturesHint);
PREP(swapBarrel);
PREP(swapBarrelAssistant);
PREP(swapBarrelCallback);
PREP(updateAmmoTemperature);
PREP(updateAmmoTemperatureThread);
PREP(updateSpareBarrelsTemperaturesThread);
PREP(updateTemperature);
PREP(updateTemperatureThread);

View File

@ -48,22 +48,64 @@ if (hasInterface) then {
GVAR(cacheAmmoData) = call CBA_fnc_createNamespace;
GVAR(cacheSilencerData) = call CBA_fnc_createNamespace;
//Add Take EH (for reload)
["CAManBase", "Take", {_this call FUNC(handleTakeEH);}] call CBA_fnc_addClassEventHandler;
//Add Take EH if required
if (GVAR(unJamOnReload) || {GVAR(cookoffCoef) > 0}) then {
["CAManBase", "Take", {_this call FUNC(handleTakeEH);}] call CBA_fnc_addClassEventHandler;
};
// Register fire event handler
["ace_firedPlayer", DFUNC(firedEH)] call CBA_fnc_addEventHandler;
// Only add eh to non local players if dispersion is enabled
if (GVAR(overheatingDispersion)) then {
if (GVAR(overheatingDispersion) || {GVAR(showParticleEffectsForEveryone)}) then {
["ace_firedPlayerNonLocal", DFUNC(firedEH)] call CBA_fnc_addEventHandler;
};
// Schedule cool down calculation of player weapons at (infrequent) regular intervals
[] call FUNC(updateTemperatureThread);
//Add event handlers and start ammo heating loop for cookoff
if (GVAR(cookoffCoef) > 0) then {
[] call FUNC(updateAmmoTemperatureThread);
// Reset ammo temperature on reload, unless the reload is a second muzzle.
["CAManBase", "Reloaded", {
params ["_unit", "_weapon", "_muzzle"];
if (_muzzle == _weapon) then {
_unit setVariable [format [QGVAR(%1_ammoTemp), _weapon], 0];
};
}] call CBA_fnc_addClassEventHandler;
};
// Install event handler to display temp when a barrel was swapped
[QGVAR(showWeaponTemperature), DFUNC(displayTemperature)] call CBA_fnc_addEventHandler;
// Install event handler to initiate an assisted barrel swap
[QGVAR(initiateSwapBarrelAssisted), DFUNC(swapBarrel)] call CBA_fnc_addEventHandler;
// Add an action to allow hot weapons to be cooled off in AceX Field Rations water sources
if (isClass(configfile >> "CfgPatches" >> "acex_field_rations")) then {
[
{acex_field_rations_enabled || CBA_missionTime > 1},
{
if (!acex_field_rations_enabled) exitWith {};
_CoolWeaponWithWaterSourceAction = [
QGVAR(CoolWeaponWithWaterSource),
LLSTRING(CoolWeaponWithWaterSource),
"\z\acex\addons\field_rations\ui\icon_water_tap.paa",
{
private _waterSource = _target getVariable ["acex_field_rations_waterSource", objNull];
[_player, _waterSource] call FUNC(coolWeaponWithWaterSource);
},
{
private _waterSource = _target getVariable ["acex_field_rations_waterSource", objNull];
[_player, _waterSource] call acex_field_rations_fnc_canDrinkFromSource;
}
] call EFUNC(interact_menu,createAction);
["acex_field_rations_helper", 0, ["acex_field_rations_waterSource"], _CoolWeaponWithWaterSourceAction] call EFUNC(interact_menu,addActionToClass);
},
[]
] call CBA_fnc_waitUntilAndExecute;
};
}] call CBA_fnc_addEventHandler;

View File

@ -30,6 +30,15 @@ if (_totalTime > 1800) exitWith {0};
//So Area = 210 * 1.1 * (mass / 7850) = mass * 0.029427 (for steel near that diameter)
private _barrelSurface = _barrelMass * 0.029427;
private _convectionRate = 25;
//provide additional cooling if swimming or raining or windy
if (ACE_player call EFUNC(common,isSwimming)) then {
_convectionRate = 500;
} else {
// this will give a convection rate between 25 (no wind or rain) and 125 (max rain and >=50 m/s wind)
_convectionRate = _convectionRate * ((linearConversion [0,1,rain,1,5,true] + (5 min (vectorMagnitude wind / 10))) / 2);
};
TRACE_4("cooling",_temperature,_totalTime,_barrelMass,_barrelSurface);
@ -38,13 +47,10 @@ while {true} do {
private _deltaTime = (_totalTime - _time) min 20;
_temperature = _temperature - (
// Convective cooling
25 * _barrelSurface * _temperature
// Radiative cooling
+ 0.4 * 5.67e-8 * _barrelSurface *
( (_temperature + 273.15)*(_temperature + 273.15)
* (_temperature + 273.15)*(_temperature + 273.15)
- 273.15 * 273.15 * 273.15 *273.15 )
// Convective cooling
_convectionRate * _barrelSurface * _temperature
// Radiative cooling
+ 0.4 * 5.67e-8 * _barrelSurface * ((_temperature + 273.15) ^ 4 - 273.15 ^ 4)
) * _deltaTime / (_barrelMass * 466);
if (_temperature < 1) exitWith {0};

View File

@ -20,7 +20,7 @@ params ["_player"];
//Get the classname of the spare barrel for the weapon
private _weaponBarrelClass = getText (configFile >> 'CfgWeapons' >> currentWeapon _player >> QGVAR(barrelClassname));
//If the weapon has no defined classname then use the ACE one
if(_weaponBarrelClass == "") then {
if (_weaponBarrelClass == "") then {
_weaponBarrelClass = "ACE_SpareBarrel";
};
//Check if the player has the barrel and the weapon can have its barrel swapped

View File

@ -18,12 +18,12 @@
params ["_unit","_weapon"];
//Check if weapon can have its barrel swapped. If not exit out of function
if( !GVAR(enabled) || {getNumber (configFile >> 'CfgWeapons' >> _weapon >> QGVAR(allowSwapBarrel)) != 1}) exitWith{false};
if ( !GVAR(enabled) || {getNumber (configFile >> 'CfgWeapons' >> _weapon >> QGVAR(allowSwapBarrel)) != 1}) exitWith{false};
//Get the classname of the spare barrel for the weapon
private _weaponBarrelClass = getText (configFile >> 'CfgWeapons' >> _weapon >> QGVAR(barrelClassname));
//If the weapon has no defined classname then use the ACE one
if(_weaponBarrelClass == "") then {
if (_weaponBarrelClass == "") then {
_weaponBarrelClass = "ACE_SpareBarrel";
};
//If the player has the spare barrel then it can be swapped

View File

@ -12,7 +12,7 @@
* None
*
* Example:
* [player, currentWeapon player] call ace_overheating_fnc_checkTemperature
* [player, player, currentWeapon player] call ace_overheating_fnc_checkTemperature
*
* Public: No
*/

View File

@ -33,6 +33,7 @@ if (_weapon in _jammedWeapons) then {
};
[_unit, _clearJamAction, 1] call EFUNC(common,doGesture);
if (_weapon == primaryWeapon _unit) then {
playSound QGVAR(fixing_rifle);
} else {
@ -42,21 +43,33 @@ if (_weapon in _jammedWeapons) then {
};
};
// Check if the jam will be successfull
// Check if the jam clearing will be successfull
if (random 1 > GVAR(unJamFailChance)) then {
// Success
_jammedWeapons = _jammedWeapons - [_weapon];
_unit setVariable [QGVAR(jammedWeapons), _jammedWeapons];
if (_jammedWeapons isEqualTo []) then {
private _id = _unit getVariable [QGVAR(JammingActionID), -1];
[_unit, "DefaultAction", _id] call EFUNC(common,removeActionEventHandler);
_unit setVariable [QGVAR(JammingActionID), -1];
};
if (GVAR(DisplayTextOnJam)) then {
[{
[{
params ["_unit", "_weapon", "_jammedWeapons"];
_jammedWeapons = _jammedWeapons - [_weapon];
_unit setVariable [QGVAR(jammedWeapons), _jammedWeapons];
// If the round is a dud eject the round
if (_unit getVariable [format [QGVAR(%1_jamType), _weapon], "None"] isEqualTo "Dud") then {
private _ammo = _unit ammo _weapon;
_unit setAmmo [_weapon, _ammo - 1];
};
_unit setVariable [format [QGVAR(%1_jamType), _weapon], "None"];
if (_jammedWeapons isEqualTo []) then {
private _id = _unit getVariable [QGVAR(JammingActionID), -1];
[_unit, "DefaultAction", _id] call EFUNC(common,removeActionEventHandler);
_unit setVariable [QGVAR(JammingActionID), -1];
};
if (GVAR(DisplayTextOnJam)) then {
[localize LSTRING(WeaponUnjammed)] call EFUNC(common,displayTextStructured);
}, [], _delay] call CBA_fnc_waitAndExecute;
};
};
}, [_unit, _weapon, _jammedWeapons], _delay] call CBA_fnc_waitAndExecute;
} else {
// Failure
if (GVAR(DisplayTextOnJam)) then {

View File

@ -0,0 +1,98 @@
#include "script_component.hpp"
/*
* Author: mharis001, Glowbal, PabstMirror, drofseh
* Cool a weapon with an item and consume the item being used to cool it.
*
* Arguments:
* 0: Target <OBJECT>
* 1: Player <OBJECT>
* 2: Item <STRING>
*
* Return Value:
* None
*
* Example:
* [ACE_player, ACE_player, "ACE_WaterBottle"] call ace_overheating_fnc_coolWeaponWithItem
*
* Public: No
*/
params ["_target", "_unit", "_item"];
private _config = configFile >> "CfgWeapons" >> _item;
// Get values
private _weapon = currentWeapon _target;
private _tempVarName = format [QGVAR(%1_temp), _weapon];
private _temperature = _target getVariable [_tempVarName, 0];
private _replacementItem = getText (_config >> "acex_field_rations_replacementItem");
private _liquidAmount = getNumber (_config >> "acex_field_rations_thirstQuenched");
private _consumeText = format [LLSTRING(CoolingWeaponWithItem), getText (configFile >> "CfgWeapons" >> _weapon >> "displayName"), getText (_config >> "displayName")];
/* // to be added when licence compatible audio can be found or recorded
private _pouringSound = QPATHTO_R(sounds\sizzling_short.ogg);
if (_temperature < 100) then {
if (_liquidAmount > 5) then {
_pouringSound = QPATHTO_R(sounds\pouring_long.ogg);
} else {
_pouringSound = QPATHTO_R(sounds\pouring_short.ogg);
};
} else {
if (_liquidAmount > 5) then {
_pouringSound = QPATHTO_R(sounds\sizzling_long.ogg);
};
};
playSound3D [_pouringSound, _target, false, AGLToASL (_target modelToWorld (_target selectionPosition "RightHand")), 5, 1, 10];
*/
private _fnc_onSuccess = {
params ["_args"];
_args params ["_target", "_unit", "_item", "_weapon", "_tempVarName", "_temperature", "_replacementItem", "_liquidAmount"];
TRACE_1("Cool weapon with item successful",_args);
// remove the item
_unit removeItem _item;
// Add replacement item if needed
if (_replacementItem != "") then {
[_unit, _replacementItem] call EFUNC(common,addToInventory);
};
// cool the weapon
private _barrelMass = _weapon call FUNC(getBarrelMass);
_temperature = [_temperature, _barrelMass, _liquidAmount * 10] call FUNC(calculateCooling);
[_target, _tempVarName, _temperature, TEMP_TOLERANCE] call EFUNC(common,setApproximateVariablePublic);
};
/*
private _fnc_onFailure = {
params ["_args","_elapsedTime"];
_args params ["_target", "_unit"];
};
*/
private _fnc_condition = {
params ["_args"];
_args params ["", "_unit", "_item"];
_item in (_unit call EFUNC(common,uniqueItems))
};
[
_liquidAmount,
[
_target,
_unit,
_item,
_weapon,
_tempVarName,
_temperature,
_replacementItem,
_liquidAmount
],
_fnc_onSuccess,
{}, //_fnc_onFailure,
_consumeText,
_fnc_condition
] call EFUNC(common,progressBar);

View File

@ -0,0 +1,117 @@
#include "script_component.hpp"
/*
* Author: mharis001, Glowbal, PabstMirror, drofseh
* Cool a weapon with an AceX water source.
*
* Arguments:
* 0: Target <OBJECT>
* 1: Player <OBJECT>
*
* Return Value:
* None
*
* Example:
* [ACE_player, WaterTank] call ace_overheating_fnc_coolWeaponWithWaterSource
*
* Public: No
*/
params ["_player", "_target"];
private _weapon = currentWeapon _player;
private _tempVarName = format [QGVAR(%1_temp), _weapon];
private _temperature = _player getVariable [_tempVarName, 0];
private _consumeText = LLSTRING(CoolingWeaponWithWaterSource);
GVAR(coolingWeaponWithWaterSource) = false;
private _fnc_onFinish = {
params ["_args"];
_args params ["_player", "_target", "_weapon", "_tempVarName"];
private _water = _target call acex_field_rations_fnc_getRemainingWater;
if (_water <= 0 && {_water != -10}) exitWith {
[
[LLSTRING(CoolWeaponNotEnoughWater)],
true
] call CBA_fnc_notify;
};
[_player, _player, currentWeapon _player] call ace_overheating_fnc_checkTemperature;
GVAR(coolingWeaponWithWaterSource) = false;
};
private _fnc_condition = {
params ["_args"];
_args params ["_player", "_target", "_weapon", "_tempVarName"];
private _temperature = _player getVariable [_tempVarName, 0];
private _water = _target call acex_field_rations_fnc_getRemainingWater;
if (_water <= 0 && {_water != -10}) exitWith {false};
if !(GVAR(coolingWeaponWithWaterSource)) then {
GVAR(coolingWeaponWithWaterSource) = true;
//Remove water from the source, unless it's unlimited
if (_water != -10) then {
[_target, _water - 1] call acex_field_rations_fnc_setRemainingWater;
};
//Cool the weapon down
private _barrelMass = _weapon call FUNC(getBarrelMass);
_temperature = [_temperature, _barrelMass, 20] call FUNC(calculateCooling);
[_player, _tempVarName, _temperature, TEMP_TOLERANCE] call EFUNC(common,setApproximateVariablePublic);
/* // to be added when licence compatible audio can be found or recorded
// water sound, either boiling or not
if (_temperature < 100) then {
[
[LLSTRING(CoolWeaponIsCool)],
true // allows the hint to be overwritten by another hint, such as a jam or another cookoff
] call CBA_fnc_notify;
playSound3D [
QPATHTO_R(sounds\pouring_short.ogg),
_player,
false,
AGLToASL (_player modelToWorld (_player selectionPosition "RightHand")),
5, 1, 10
];
} else {
playSound3D [
QPATHTO_R(sounds\sizzling_short.ogg),
_player,
false,
AGLToASL (_player modelToWorld (_player selectionPosition "RightHand")),
5, 1, 10
];
};
*/
// wait 1 second before allowing cooling to happen again
[
{
GVAR(coolingWeaponWithWaterSource) = false;
},
[],
1
] call CBA_fnc_waitAndExecute;
};
true
};
[
1 max (_temperature / 10),
[
_player,
_target,
_weapon,
_tempVarName
],
_fnc_onFinish,
_fnc_onFinish,
_consumeText,
_fnc_condition
] call EFUNC(common,progressBar);

View File

@ -20,7 +20,7 @@ TRACE_10("firedEH:",_unit, _weapon, _muzzle, _mode, _ammo, _magazine, _projectil
BEGIN_COUNTER(firedEH);
if ((_unit distance ACE_player) > 3000
if ((_unit distance ACE_player) > GVAR(particleEffectsAndDispersionDistance)
|| {(_muzzle != (primaryWeapon _unit)) && {_muzzle != (handgunWeapon _unit)}}) exitWith { // Only rifle or pistol muzzles (ignore grenades / GLs)
END_COUNTER(firedEH);
};
@ -54,12 +54,12 @@ if (_scaledTemperature > 0.1) then {
[_projectile, _dispersionX * _dispersion, _dispersionY * _dispersion, _slowdownFactor * vectorMagnitude (velocity _projectile)] call EFUNC(common,changeProjectileDirection);
TRACE_PROJECTILE_INFO(_projectile);
};
// Particle Effects
if (GVAR(showParticleEffects)
&& {GVAR(showParticleEffectsForEveryone) || {_unit == ACE_player} || {_unit distance ACE_player <= 20}}
&& {CBA_missionTime > (_unit getVariable [QGVAR(lastDrop), -1000]) + 0.40}) then {
_unit setVariable [QGVAR(lastDrop), CBA_missionTime];
private _direction = (_unit weaponDirection _weapon) vectorMultiply 0.25;
@ -94,9 +94,24 @@ if ((_unit ammo _weapon) % 3 == 0) then {
_this call FUNC(overheat);
};
// reset cookoff heat
if (GVAR(cookoffCoef) > 0) then {
_unit setVariable [format [QGVAR(%1_ammoTemp), _weapon], 0];
[_unit, _weapon, _temperature] call FUNC(updateAmmoTemperature);
};
// decrease time to next shot as heat increases, value is a coef where 1 is unchanged and 0 is instant, 0.8 is a 25% faster ROF.
// this could be filtered by weapon type, but I think the heat gain and rate of fire on non-automatic weapons is low enough not to bother
if (GVAR(overheatingRateOfFire)) then {
_unit setWeaponReloadingTime [_unit, _muzzle, linearConversion [0, 0.5, _scaledTemperature, 1, 0.909, true]];
};
// Don't bother with jamming if coef makes the chance 0.
if (GVAR(jamChanceCoef) == 0) exitWith {END_COUNTER(firedEH);};
private _value = 5 * _scaledTemperature;
private _array = [0.5, 1, 2, 8, 20, 150];
_jamChance = _jamChance * linearConversion [0, 1, _value % 1, _array select floor _value, _array select ceil _value];
_jamChance = _jamChance * GVAR(jamChanceCoef) * linearConversion [0, 1, _value % 1, _array select floor _value, _array select ceil _value];
TRACE_3("check for random jam",_unit,_weapon,_jamChance);

View File

@ -0,0 +1,20 @@
#include "script_component.hpp"
/*
* Author: mharis001, Glowbal, PabstMirror, drofseh
* Get the mass of the weapons barrel.
*
* Arguments:
* 0: Weapon <STRING>
*
* Return Value:
* Barrel Mass <NUMBER>
*
* Example:
* [currentWeapon ACE_player] call ace_overheating_fnc_getBarrelMass
*
* Public: No
*/
params ["_weapon"];
METAL_MASS_RATIO * (getNumber (configFile >> "CfgWeapons" >> _weapon >> "WeaponSlotsInfo" >> "mass") / 22.0) max 1.0;

View File

@ -0,0 +1,41 @@
#include "script_component.hpp"
/*
* Author: mharis001, Glowbal, PabstMirror
* Returns children actions for consumable items in player's inventory.
*
* Arguments:
* 0: Player <OBJECT>
*
* Return Value:
* Actions <ARRAY>
*
* Example:
* [_unit] call ace_overheating_fnc_getConsumableChildren
*
* Public: No
*/
params ["_unit"];
private _fnc_getActions = {
TRACE_1("Creating overheating consumable item actions",_unit);
private _actions = [];
private _cfgWeapons = configFile >> "CfgWeapons";
{
private _config = _cfgWeapons >> _x;
if (getNumber (_config >> "acex_field_rations_thirstQuenched") > 0) then {
private _displayName = getText (_config >> "displayName");
private _picture = getText (_config >> "picture");
// Exec next frame so closing interaction menu doesn't block progressBar
private _action = [_x, _displayName, _picture, {[FUNC(coolWeaponWithItem), _this] call CBA_fnc_execNextFrame}, {true}, {}, _x] call EFUNC(interact_menu,createAction);
_actions pushBack [_action, [], _unit];
};
} forEach (_unit call EFUNC(common,uniqueItems));
_actions
};
[[], _fnc_getActions, _unit, QGVAR(overheatingConsumableActionsCache), 9999, "cba_events_loadoutEvent"] call EFUNC(common,cachedCall);

View File

@ -65,8 +65,16 @@ if (isArray _property) then {
};
};
// for cookoff
private _modes = getArray (configFile >> "CfgWeapons" >> _weapon >> "modes");
private _muzzle = getArray (configFile >> "CfgWeapons" >> _weapon >> "muzzles") select 0;
if (_muzzle == "this") then {
_muzzle = _weapon;
};
private _reloadTime = getNumber (configfile >> "CfgWeapons" >> _weapon >> (_modes select 0) >> "reloadTime");
// Cache the values
_weaponData = [_dispersion, _slowdownFactor, _jamChance];
_weaponData = [_dispersion, _slowdownFactor, _jamChance, _modes, _muzzle, _reloadTime];
TRACE_2("building cache",_weapon,_weaponData);
GVAR(cacheWeaponData) setVariable [_weapon, _weaponData];

View File

@ -18,16 +18,20 @@
* Public: No
*/
if !(GVAR(unJamOnreload)) exitWith {};
params ["_unit", "_container", "_item"];
TRACE_3("params",_unit,_container,_item);
if ((_unit == ACE_player)
&& {_container in [uniformContainer _unit, vestContainer _unit, backpackContainer _unit]}
&& {_item == currentMagazine _unit}) then { //Todo: should this be any valid magazine for any jammed gun?
&& {_container in [uniformContainer _unit, vestContainer _unit, backpackContainer _unit]}
&& {_item == currentMagazine _unit}
) then { //Todo: should this be any valid magazine for any jammed gun?
TRACE_1("clearing jam",currentWeapon _unit);
[_unit, currentWeapon _unit, true] call FUNC(clearJam)
if (GVAR(unJamOnReload)) then {
TRACE_1("clearing jam",currentWeapon _unit);
[_unit, currentWeapon _unit, true] call FUNC(clearJam);
};
if (GVAR(cookoffCoef) > 0) then {
_unit setVariable [format [QGVAR(%1_ammoTemp), currentWeapon _unit], 0];
};
};

View File

@ -21,24 +21,45 @@ TRACE_2("params",_unit,_weapon);
// don't jam a weapon with no rounds left
private _ammo = _unit ammo _weapon;
if (_ammo == 0) exitWith {};
if (_ammo < 1) exitWith {};
private _jammedWeapons = _unit getVariable [QGVAR(jammedWeapons), []];
_jammedWeapons pushBack _weapon;
_unit setVariable [QGVAR(jammedWeapons), _jammedWeapons];
// Get jam types, select one from available types
// Cookoffs only happen on Fire and Dud, dud rounds are lost on jam clear.
// Reduce chance of duds as temp increases (functionally increasing the chance of the others but with fewer commands)
private _temp = 1 max (_unit getVariable [format [QGVAR(%1_temp), _weapon], 0]);
private _jamTypesAllowed = getArray (configFile >> 'CfgWeapons' >> currentWeapon _player >> QGVAR(jamTypesAllowed));
if (_jamTypesAllowed == []) then {
_jamTypesAllowed = ["Eject", 1, "Extract", 1, "Feed", 1, "Fire", 1, "Dud", (5 / (_temp / 5))];
} else {
for "_i" from count _jamTypesAllowed to 1 step -1 do {
private _jamCurretType = _jamTypesAllowed select _i;
if !(_jamCurretType in ["Eject", "Extract", "Feed", "Fire", "Dud"]) exitWith { // check config values and switch to default values if unusual value found
ERROR_2("Weapon '%1' has unexpected value %2 in QQGVAR(jamTypesAllowed). Expected values are 'Eject', 'Extract', 'Feed', 'Fire', 'Dud'.",_weapon,_jamCurretType);
_jamTypesAllowed = ["Eject", 1, "Extract", 1, "Feed", 1, "Fire", 1, "Dud", (5 / (_temp / 5))];
};
if (_jamCurretType == "Dud") then {
_jamTypesAllowed insert [_i, [5 / (_temp / 5)]];
} else {
_jamTypesAllowed insert [_i, [1]];
};
};
};
_unit setVariable [format [QGVAR(%1_jamType), _weapon], selectRandomWeighted _jamTypesAllowed];
// Stop current burst
if (_ammo > 0) then {
_unit setAmmo [_weapon, 0];
// this is to re-activate the 'DefaultAction', so you can jam a weapon while full auto shootin
[{
params ["_unit", "_weapon", "_ammo"];
_unit setAmmo [_weapon, _ammo];
}, [_unit, _weapon, _ammo]] call CBA_fnc_execNextFrame;
};
_unit setAmmo [_weapon, 0];
// this is to re-activate the 'DefaultAction', so you can jam a weapon while full auto shooting
[{
params ["_unit", "_weapon", "_ammo"];
_unit setAmmo [_weapon, _ammo];
}, [_unit, _weapon, _ammo]] call CBA_fnc_execNextFrame;
if (_weapon == primaryWeapon _unit) then {
playSound QGVAR(jamming_rifle);
@ -53,20 +74,32 @@ GVAR(knowAboutJam) = false;
["ace_weaponJammed", [_unit,_weapon]] call CBA_fnc_localEvent;
if (_unit getVariable [QGVAR(JammingActionID), -1] == -1) then {
private _condition = {
[_this select 1] call CBA_fnc_canUseWeapon
&& {currentMuzzle (_this select 1) in ((_this select 1) getVariable [QGVAR(jammedWeapons), []])}
&& {!(currentMuzzle (_this select 1) in ((_this select 1) getVariable [QEGVAR(safemode,safedWeapons), []]))}
private _unit = _this select 1;
[_unit] call CBA_fnc_canUseWeapon
&& {currentMuzzle _unit in (_unit getVariable [QGVAR(jammedWeapons), []])}
&& {!(currentMuzzle _unit in (_unit getVariable [QEGVAR(safemode,safedWeapons), []]))}
};
private _statement = {
playSound3D ["a3\sounds_f\weapons\Other\dry9.wss", _this select 0, false, eyePos (_this select 0), 1, 1, 15];
params ["_zero","_one"];
if (!(missionNamespace getVariable [QGVAR(knowAboutJam), false]) && {(_this select 1) ammo currentWeapon (_this select 1) > 0} && {GVAR(DisplayTextOnJam)}) then {
[localize LSTRING(WeaponJammed)] call EFUNC(common,displayTextStructured);
playSound3D ["a3\sounds_f\weapons\Other\dry9.wss", _zero, false, eyePos _zero, 1, 1, 15];
if (!(missionNamespace getVariable [QGVAR(knowAboutJam), false]) && {_one ammo currentWeapon _one > 0} && {GVAR(DisplayTextOnJam)}) then {
private _jamType = _one getVariable [format [QGVAR(%1_jamType), currentWeapon _one], "None"];
private _jamMessage = localize LSTRING(FailureToFire);
switch true do {
case (_jamType isEqualTo "Eject"): {_jamMessage = localize LSTRING(FailureToEject)};
case (_jamType isEqualTo "Extract"): {_jamMessage = localize LSTRING(FailureToExtract)};
case (_jamType isEqualTo "Feed"): {_jamMessage = localize LSTRING(FailureToFeed)};
};
[
[localize LSTRING(WeaponJammed)],
[_jamMessage]
] call CBA_fnc_notify;
GVAR(knowAboutJam) = true;
};
};

View File

@ -25,7 +25,7 @@ params ["_assistant", "_gunner", "_weapon", "_weaponTemp", "_barrelMass"];
TRACE_5("loadCoolestSpareBarrel1",_assistant,_gunner,_weapon,_weaponTemp,_barrelMass);
private _weaponBarrelClass = getText (configFile >> 'CfgWeapons' >> _weapon >> QGVAR(barrelClassname));
//If the weapon has no defined classname then use the ACE one
if(_weaponBarrelClass == "") then {
if (_weaponBarrelClass == "") then {
_weaponBarrelClass = "ACE_SpareBarrel";
};
// Find all spare barrel the player has

View File

@ -27,7 +27,7 @@ private _weapon = currentWeapon _player;
//Get the classname of the spare barrel of that weapon
private _weaponBarrelClass = getText (configFile >> 'CfgWeapons' >> _weapon >> QGVAR(barrelClassname));
//If the weapon has no defined classname then use the ACE one
if(_weaponBarrelClass == "") then {
if (_weaponBarrelClass == "") then {
_weaponBarrelClass = "ACE_SpareBarrel";
};
private _allBarrels = [_unit, _weaponBarrelClass] call CBA_fnc_getMagazineIndex;

View File

@ -26,11 +26,15 @@ if (_assistant isEqualTo _gunner) then {
playSound "ACE_BarrelSwap";
};
if (GVAR(unJamOnSwapBarrel) && {[_gunner] call FUNC(canUnjam)}) then {
[_gunner, currentMuzzle _gunner, true] call FUNC(clearJam);
};
// don't consume the barrel, but rotate through them.
[localize LSTRING(SwappedBarrel), QPATHTOF(UI\spare_barrel_ca.paa)] call EFUNC(common,displayTextPicture);
private _temp = _gunner getVariable [format [QGVAR(%1_temp), _weapon], 0];
private _barrelMass = METAL_MASS_RATIO * (getNumber (configFile >> "CfgWeapons" >> _weapon >> "WeaponSlotsInfo" >> "mass") / 22.0) max 1.0;
private _barrelMass = _weapon call FUNC(getBarrelMass);
// Instruct the server to load the coolest spare barrel into the weapon and
// store the removed barrel with the former weapon temperature. The server

View File

@ -0,0 +1,106 @@
#include "script_component.hpp"
/*
* Author: drofseh
* Update temperature of the round in the chamber and determine if a cookoff should occur.
*
* Arguments:
* 0: Unit <OBJECT>
* 1: Weapon <STRING>
* 2: Barrel Temperature <STRING>
*
* Return Value:
* Current ammunition temperature <NUMBER>
*
* Example:
* [player, currentWeapon player, 600] call ace_overheating_fnc_updateAmmoTemperature
*
* Public: No
*/
params ["_unit", "_weapon", "_barrelTemperature"];
TRACE_3("params",_unit,_weapon,_barrelTemperature);
private _closedBolt = getNumber (configFile >> "CfgWeapons" >> _weapon >> QGVAR(closedBolt));
private _canUnjam = [_unit] call FUNC(canUnjam);
// Skip if no ammo in chamber
if (
_unit ammo _weapon < 1
// closed bolt, and jammed and type not failure to fire
|| {_closedBolt == 1 && {_canUnjam} && {!(_unit getVariable [format [QGVAR(%1_jamType), _weapon], "None"] in ["Fire","Dud"])}}
// open bolt, and not jammed, or jammed and type not failure to fire
|| {_closedBolt == 0 && {!_canUnjam || {_canUnjam && {!(_unit getVariable [format [QGVAR(%1_jamType), _weapon], "None"] in ["Fire","Dud"])}}}}
) exitWith {
_unit setVariable [format [QGVAR(%1_ammoTemp), _weapon], 0];
0
};
private _ammoTempVarName = format [QGVAR(%1_ammoTemp), _weapon];
private _ammoTemperature = _unit getVariable [_ammoTempVarName, 0];
// heat or cool the ammo
if (_ammoTemperature < _barrelTemperature) then {
// this is functional and feels ok, but someone please do better heat transfer math here, my head hurts.
private _temperatureDifference = _barrelTemperature - _ammoTemperature;
_ammoTemperature = _ammoTemperature + (1 max ((_temperatureDifference / 2.75) - 100));
} else {
_ammoTemperature = _barrelTemperature;
};
// check for cook off
if (_ammoTemperature > (GUNPOWDER_IGNITION_TEMP * GVAR(cookoffCoef))) then {
// a weapon with a failure to fire or dud type jam will be unjammed from cooking off
// this is first so that the fired event from the cookoff can also cause a jam
private _jamType = _unit getVariable [format [QGVAR(%1_jamType), _weapon], "None"];
if (_canUnjam && {_jamType in ["Fire","Dud"]}) then {
[_unit, currentMuzzle _unit, true] call FUNC(clearJam);
// clearJam will remove a dud round, but so will the forced fire, so give back the lost round and shoot it
if (_jamType isEqualTo "Dud") then {
private _ammo = _unit ammo _weapon;
_unit setAmmo [_weapon, _ammo + 1];
};
};
// get valid mode and muzzle for the main weapon, we don't want the cookoff to come from an underbarrel launcher
([_weapon] call FUNC(getWeaponData)) params ["", "", "", "_modes", "_muzzle", "_reloadTime"];
// get an appropriate firemode and muzzle, cache the current muzzle
// trying to match firemodes and switching back to the cached muzzle will hide the change from the player and prevent unexpected mode/muzzle changes (going from full auto to semi auto, or from underbarrel GL to rifle for example)
private _muzzleCache = currentMuzzle _unit;
private _mode = currentWeaponMode _unit;
if !(_mode in _modes) then {
_mode = _modes select 0;
};
// delay cookoff to ensure any previous animation from a fired event is finished
[
{
params ["_unit", "_muzzleCache", "_mode", "_muzzle"];
// fire the cookoff
_unit forceWeaponFire [_muzzle, _mode];
// switch back to the cached muzzle if required
if (_muzzle != _muzzleCache) then {
_unit selectWeapon _muzzleCache;
};
[
[localize LSTRING(WeaponCookedOff)],
true // allows the hint to be overwritten by another hint, such as a jam or another cookoff
] call CBA_fnc_notify;
},
[_unit, _muzzleCache, _mode, _muzzle],
_reloadTime
] call CBA_fnc_waitAndExecute;
// if the cookoff happened then the next round should start at 0
_ammoTemperature = 0;
};
_unit setVariable [_ammoTempVarName, _ammoTemperature];
_ammoTemperature

View File

@ -0,0 +1,25 @@
#include "script_component.hpp"
/*
* Author: esteldunedain & drofseh
* Update .
*
* Arguments:
* None
*
* Return Value:
* None
*
* Example:
* [] call ace_overheating_fnc_updateAmmoTemperatureThread
*
* Public: No
*/
private _currentWeapon = currentWeapon ACE_player;
if ((_currentWeapon != "") && {_currentWeapon == primaryWeapon ACE_player || {_currentWeapon == handgunWeapon ACE_player}}) then {
private _temperature = ACE_player getVariable [format [QGVAR(%1_temp), _currentWeapon], 0];
[ACE_player, _currentWeapon, _temperature] call FUNC(updateAmmoTemperature);
};
// Schedule for execution again after 1 seconds. A quick loop is needed for ammo temperature in order to have faster cookoffs at higher barrel temps
[DFUNC(updateAmmoTemperatureThread), [], 1] call CBA_fnc_waitAndExecute;

View File

@ -27,7 +27,7 @@ private _timeVarName = format [QGVAR(%1_time), _weapon];
private _temperature = _unit getVariable [_tempVarName, 0];
private _lastTime = _unit getVariable [_timeVarName, 0];
private _barrelMass = METAL_MASS_RATIO * (getNumber (configFile >> "CfgWeapons" >> _weapon >> "WeaponSlotsInfo" >> "mass") / 22.0) max 1.0;
private _barrelMass = _weapon call FUNC(getBarrelMass);
// Calculate cooling
_temperature = [_temperature, _barrelMass, CBA_missionTime - _lastTime] call FUNC(calculateCooling);
@ -35,7 +35,7 @@ _temperature = [_temperature, _barrelMass, CBA_missionTime - _lastTime] call FUN
TRACE_1("cooledTo",_temperature);
// Calculate heating
// Steel Heat Capacity = 466 J/(Kg.K)
_temperature = _temperature + _heatIncrement / (_barrelMass * 466);
_temperature = _temperature + _heatIncrement * GVAR(heatCoef) / (_barrelMass * 466);
// Publish the temperature variable
[_unit, _tempVarName, _temperature, TEMP_TOLERANCE] call EFUNC(common,setApproximateVariablePublic);

View File

@ -15,9 +15,20 @@
* Public: No
*/
private _currentWeapon = currentWeapon ACE_player;
if ((_currentWeapon != "") && {_currentWeapon == primaryWeapon ACE_player || {_currentWeapon == handgunWeapon ACE_player}}) then {
[ACE_player, _currentWeapon, 0] call FUNC(updateTemperature);
if (ACE_player call EFUNC(common,isSwimming)) then { // cool off both weapons while swimming because currentWeapon == ""
private _primaryWeapon = primaryWeapon ACE_player;
private _handgunWeapon = handgunWeapon ACE_player;
if (_primaryWeapon != "") then {
[ACE_player, _primaryWeapon, 0] call FUNC(updateTemperature);
};
if (_handgunWeapon != "") then {
[ACE_player, _handgunWeapon, 0] call FUNC(updateTemperature);
};
} else {
private _currentWeapon = currentWeapon ACE_player;
if ((_currentWeapon != "") && {_currentWeapon == primaryWeapon ACE_player || {_currentWeapon == handgunWeapon ACE_player}}) then {
[ACE_player, _currentWeapon, 0] call FUNC(updateTemperature);
};
};
// Schedule for execution again after 5 seconds

View File

@ -5,6 +5,16 @@ private _category = format ["ACE %1", localize LSTRING(DisplayName)];
[LSTRING(enabled_displayName), LSTRING(enabled_description)],
_category,
true,
1,
{},
true
] call CBA_fnc_addSetting;
[
QGVAR(heatCoef), "SLIDER",
[LSTRING(heatCoef_displayName), LSTRING(heatCoef_description)],
_category,
[0, 5, 1, 2],
1
] call CBA_fnc_addSetting;
@ -21,7 +31,9 @@ private _category = format ["ACE %1", localize LSTRING(DisplayName)];
[LSTRING(showParticleEffectsForEveryone_displayName), LSTRING(showParticleEffectsForEveryone_description)],
_category,
false,
0
0,
{},
true
] call CBA_fnc_addSetting;
[
@ -29,6 +41,24 @@ private _category = format ["ACE %1", localize LSTRING(DisplayName)];
[LSTRING(overheatingDispersion_displayName), LSTRING(overheatingDispersion_description)],
_category,
true,
1,
{},
true
] call CBA_fnc_addSetting;
[
QGVAR(particleEffectsAndDispersionDistance), "SLIDER",
[LSTRING(particleEffectsAndDispersionDistance_displayName), LSTRING(particleEffectsAndDispersionDistance_description)],
_category,
[1, 5000, 3000, 0],
0
] call CBA_fnc_addSetting;
[
QGVAR(overheatingRateOfFire), "CHECKBOX",
[LSTRING(overheatingRateOfFire_displayName), LSTRING(overheatingRateOfFire_description)],
_category,
true,
1
] call CBA_fnc_addSetting;
@ -40,11 +70,29 @@ private _category = format ["ACE %1", localize LSTRING(DisplayName)];
0
] call CBA_fnc_addSetting;
[
QGVAR(jamChanceCoef), "SLIDER",
[LSTRING(jamChanceCoef_displayName), LSTRING(jamChanceCoef_description)],
_category,
[0, 5, 1, 2],
0
] call CBA_fnc_addSetting;
[
QGVAR(unJamOnreload), "CHECKBOX",
[LSTRING(unJamOnreload_displayName), LSTRING(unJamOnreload_description)],
_category,
false,
1,
{},
true
] call CBA_fnc_addSetting;
[
QGVAR(unJamOnSwapBarrel), "CHECKBOX",
[LSTRING(unJamOnSwapBarrel_displayName), LSTRING(unJamOnSwapBarrel_description)],
_category,
false,
1
] call CBA_fnc_addSetting;
@ -55,3 +103,13 @@ private _category = format ["ACE %1", localize LSTRING(DisplayName)];
[0, 1, 0.1, 2],
1
] call CBA_fnc_addSetting;
[
QGVAR(cookoffCoef), "SLIDER",
[LSTRING(cookoffCoef_displayName), LSTRING(cookoffCoef_description)],
_category,
[0, 5, 1, 2],
1,
{},
true
] call CBA_fnc_addSetting;

View File

@ -18,6 +18,7 @@
#define TEMP_TOLERANCE 50
#define METAL_MASS_RATIO 0.55
#define GUNPOWDER_IGNITION_TEMP 180
#ifdef DEBUG_MODE_FULL
#define TRACE_PROJECTILE_INFO(BULLET) _vdir = vectorNormalized velocity BULLET; _dir = (_vdir select 0) atan2 (_vdir select 1); _up = asin (_vdir select 2); _mv = vectorMagnitude velocity BULLET; TRACE_3("adjusted projectile",_dir,_up,_mv);

View File

@ -16,8 +16,44 @@
<French>Surchauffe</French>
<Czech>Přehřívání</Czech>
</Key>
<Key ID="STR_ACE_Overheating_enabled_displayName">
<English>Overheating Enabled</English>
<German>Überhitzung aktiviert</German>
<Spanish>Activado Sobrecalentamiento</Spanish>
<Portuguese>Superaquecimento ativado</Portuguese>
<French>Surchauffe activée</French>
<Russian>Перегрев включен</Russian>
<Czech>Přehřívání povoleno</Czech>
<Japanese>過熱を有効化</Japanese>
<Polish>Przegrzewanie włączone</Polish>
<Korean>과열 활성화</Korean>
<Italian>Surriscaldamento Abilitato</Italian>
<Chinesesimp>启用过热</Chinesesimp>
<Chinese>啟用過熱</Chinese>
</Key>
<Key ID="STR_ACE_Overheating_enabled_description">
<English>Master enable for the overheating/jamming module</English>
<Spanish>Activación maestra para Sobrecalentamiento</Spanish>
<Portuguese>Chave mestra para o módulo de superaquecimento/emperramento</Portuguese>
<French>Active le module de surchauffe/d'enrayement.</French>
<Russian>Главный включатель для модуля перегрева/заклинивания</Russian>
<Japanese>過熱と弾詰まりモジュールを全て有効化します</Japanese>
<Polish>Główny włącznik modułu przegrzewania/zacinania się broni</Polish>
<German>Hauptschalter, um die Überhitzung-/Ladehemmung-Module zu aktivieren</German>
<Korean>과열/탄걸림 최종 활성화</Korean>
<Italian>Abilitazione master per il modulo di surriscaldamento / inceppamento</Italian>
<Chinesesimp>启用枪管过热/干扰模块</Chinesesimp>
<Chinese>啟用槍管過熱/干擾模塊</Chinese>
<Czech>Hlavní přepínač pro modul přehřívání/zasekávání</Czech>
</Key>
<Key ID="STR_ACE_Overheating_heatCoef_displayName">
<English>Heating Coefficient</English>
</Key>
<Key ID="STR_ACE_Overheating_heatCoef_description">
<English>Coefficient for the amount of heat a weapon generates per shot.\nHigher value increases heat.</English>
</Key>
<Key ID="STR_ACE_Overheating_DisplayTextOnJam_displayName">
<English>Display text on jam</English>
<English>Display Text on Jam</English>
<German>Zeige Text bei Ladehemmung</German>
<Spanish>Mostrar texto al encasquillarse</Spanish>
<Russian>Показывать текст, когда клинит оружие</Russian>
@ -81,7 +117,7 @@
<Hungarian>Fegyvertúlmelegedést okozó részecskehatások mutatása</Hungarian>
</Key>
<Key ID="STR_ACE_Overheating_showParticleEffectsForEveryone_displayName">
<English>Overheating Particle Effects for everyone</English>
<English>Overheating Particle Effects for Everyone</English>
<German>Zeige Partikeleffekt bei Überhitzung für jeden</German>
<Polish>Pokaż efekty cząsteczkowe dla wszystkich</Polish>
<Italian>Effetti Particellari Surriscaldamento per tutti</Italian>
@ -144,8 +180,26 @@
<Chinese>過熱的武器將會有打不準和減少射擊初速的情況。適用於所有玩家</Chinese>
<Hungarian>A túlmelegedett fegyverek kevésbé lesznek pontosak és csökkent a lövés sebessége. Minden játékosra vonatkozik.</Hungarian>
</Key>
<Key ID="STR_ACE_Overheating_unJamOnreload_displayName">
<English>Unjam weapon on reload</English>
<Key ID="STR_ACE_Overheating_particleEffectsAndDispersionDistance_displayName">
<English>Distance for Effects and Dispersion</English>
</Key>
<Key ID="STR_ACE_Overheating_particleEffectsAndDispersionDistance_description">
<English>The distance, in meters, from the player within which overheating particle effects and dispersion are visible.</English>
</Key>
<Key ID="STR_ACE_Overheating_overheatingRateOfFire_displayName">
<English>Heat Increases Fire Rate</English>
</Key>
<Key ID="STR_ACE_Overheating_overheatingRateOfFire_description">
<English>As weapons heat up, their rate of fire increases by up to 10%.</English>
</Key>
<Key ID="STR_ACE_Overheating_jamChanceCoef_displayName">
<English>Jam Chance Coefficient</English>
</Key>
<Key ID="STR_ACE_Overheating_jamChanceCoef_description">
<English>Coefficient for the chance that a weapon will jam from overheating.\nHigher value make jams more likely.\nSet to 0 to disable jamming.</English>
</Key>
<Key ID="STR_ACE_Overheating_unJamOnReload_displayName">
<English>Unjam Weapon on Reload</English>
<German>Behebt Ladehemmung beim Nachladen</German>
<Spanish>Desencasquillar el arma al recargar.</Spanish>
<Polish>Usuń zacięcie przy przeładowaniu</Polish>
@ -160,7 +214,7 @@
<Chinese>重裝彈匣以解決卡彈</Chinese>
<Hungarian>Távolítsa el az akadályt újratöltéskor</Hungarian>
</Key>
<Key ID="STR_ACE_Overheating_unJamOnreload_description">
<Key ID="STR_ACE_Overheating_unJamOnReload_description">
<English>Reloading clears a weapon jam.</English>
<German>Nachladen behebt eine Ladehemmung.</German>
<Spanish>Recargar el arma la desencasquilla.</Spanish>
@ -176,8 +230,14 @@
<Chinese>利用重裝彈匣來解決卡彈</Chinese>
<Hungarian>Az újratöltés megszünteti a fegyver elakadását.</Hungarian>
</Key>
<Key ID="STR_ACE_Overheating_unJamOnSwapBarrel_displayName">
<English>Unjam on Barrel Swap</English>
</Key>
<Key ID="STR_ACE_Overheating_unJamOnSwapBarrel_description">
<English>Controls whether swapping barrels clears a weapon jam.</English>
</Key>
<Key ID="STR_ACE_Overheating_unJamFailChance_displayName">
<English>Chance of unjam failing</English>
<English>Chance of Unjam Failing</English>
<German>Wahrscheinlichkeit, dass Ladehemmung nicht behoben wird</German>
<Spanish>Probabilidad de falla al desencasquillar.</Spanish>
<Polish>Szansa na porażkę usuw. zacięcia</Polish>
@ -208,6 +268,12 @@
<Chinese>清除卡彈時有可能會失敗,需要反覆進行清槍。</Chinese>
<Hungarian>Valószínűsége annak, hogy egy akadály eltávolítás művelet kudarcot vall, megismétlést igényel.</Hungarian>
</Key>
<Key ID="STR_ACE_Overheating_cookoffCoef_displayName">
<English>Overheating Cookoff Coefficient</English>
</Key>
<Key ID="STR_ACE_Overheating_cookoffCoef_description">
<English>Coefficient for the heat required for cookoffs to occur.\nHigher values require more heat to cookoff.\nSet to 0 to disable cookoff.</English>
</Key>
<Key ID="STR_ACE_Overheating_SpareBarrelName">
<English>Spare barrel</English>
<German>Ersatzlauf</German>
@ -256,6 +322,21 @@
<Chinesesimp>武器卡弹!</Chinesesimp>
<Chinese>武器卡彈!</Chinese>
</Key>
<Key ID="STR_ACE_Overheating_WeaponCookedOff">
<English>Weapon cooked off!</English>
</Key>
<Key ID="STR_ACE_Overheating_FailureToEject">
<English>Failure to eject.</English>
</Key>
<Key ID="STR_ACE_Overheating_FailureToExtract">
<English>Failure to extract.</English>
</Key>
<Key ID="STR_ACE_Overheating_FailureToFeed">
<English>Failure to feed.</English>
</Key>
<Key ID="STR_ACE_Overheating_FailureToFire">
<English>Failure to fire.</English>
</Key>
<Key ID="STR_ACE_Overheating_UnjamWeapon">
<English>Clear jam</English>
<German>Ladehemmung beheben</German>
@ -429,6 +510,24 @@
<Chinesesimp>正在检查枪管温度...</Chinesesimp>
<Chinese>檢查槍管溫度中...</Chinese>
</Key>
<Key ID="STR_ACE_Overheating_CoolWeaponWithItem">
<English>Cool weapon with...</English>
</Key>
<Key ID="STR_ACE_Overheating_CoolingWeaponWithItem">
<English>Cooling %1 with %2.</English>
</Key>
<Key ID="STR_ACE_Overheating_CoolWeaponWithWaterSource">
<English>Cool weapon in water source.</English>
</Key>
<Key ID="STR_ACE_Overheating_CoolingWeaponWithWaterSource">
<English>Cooling weapon in water source.</English>
</Key>
<Key ID="STR_ACE_Overheating_CoolWeaponNotEnoughWater">
<English>Container doesn't have enough water.</English>
</Key>
<Key ID="STR_ACE_Overheating_CoolWeaponIsCool">
<English>Weapon is cool enough the water has stopped boiling.</English>
</Key>
<Key ID="STR_ACE_Overheating_Temperature">
<English>Temperature</English>
<German>Temperatur</German>
@ -520,35 +619,5 @@
<Chinesesimp>备用枪管温度超级热</Chinesesimp>
<Chinese>備用槍管溫度超級熱</Chinese>
</Key>
<Key ID="STR_ACE_Overheating_enabled_displayName">
<English>Overheating Enabled</English>
<German>Überhitzung aktiviert</German>
<Spanish>Activado Sobrecalentamiento</Spanish>
<Portuguese>Superaquecimento ativado</Portuguese>
<French>Surchauffe activée</French>
<Russian>Перегрев включен</Russian>
<Czech>Přehřívání povoleno</Czech>
<Japanese>過熱を有効化</Japanese>
<Polish>Przegrzewanie włączone</Polish>
<Korean>과열 활성화</Korean>
<Italian>Surriscaldamento Abilitato</Italian>
<Chinesesimp>启用过热</Chinesesimp>
<Chinese>啟用過熱</Chinese>
</Key>
<Key ID="STR_ACE_Overheating_enabled_description">
<English>Master enable for the overheating/jamming module</English>
<Spanish>Activación maestra para Sobrecalentamiento</Spanish>
<Portuguese>Chave mestra para o módulo de superaquecimento/emperramento</Portuguese>
<French>Active le module de surchauffe/d'enrayement.</French>
<Russian>Главный включатель для модуля перегрева/заклинивания</Russian>
<Japanese>過熱と弾詰まりモジュールを全て有効化します</Japanese>
<Polish>Główny włącznik modułu przegrzewania/zacinania się broni</Polish>
<German>Hauptschalter, um die Überhitzung-/Ladehemmung-Module zu aktivieren</German>
<Korean>과열/탄걸림 최종 활성화</Korean>
<Italian>Abilitazione master per il modulo di surriscaldamento / inceppamento</Italian>
<Chinesesimp>启用枪管过热/干扰模块</Chinesesimp>
<Chinese>啟用槍管過熱/干擾模塊</Chinese>
<Czech>Hlavní přepínač pro modul přehřívání/zasekávání</Czech>
</Key>
</Package>
</Project>

View File

@ -49,6 +49,7 @@ ace_recoil_shakemultiplier
ace_overpressure_angle
ace_overpressure_range
ace_overpressure_damage
ace_overheating_closedbolt
ace_overheating_dispersion
ace_overheating_slowdownfactor
ace_overheating_jamchance

View File

@ -15,27 +15,36 @@ version:
## 1. Overview
### 1.1 Weapon Jamming
Adds a probability to jam a weapon when firing. Jams can be cleared by reloading or by using the clear jam-key.
Adds a probability to jam a weapon when firing. Jams can be cleared by reloading, using the clear jam-key, or using the self interaction menu.
### 1.2 Temperature simulation
Introduces weapon temperature simulation depending on weapon and bullet mass. Hot weapons are more prone to jamming. Depending on weapon type the accuracy and in extreme cases the muzzle velocity might be reduced on high temperatures. Adds smoke puff and heat refraction effects to indicate this.
### 1.3 Spare barrels
Adds the ability to changes barrels on machine guns to compensate for those effects.
Introduces weapon temperature simulation depending on weapon and bullet mass. Hot weapons are more prone to jamming and will have an increase in their cyclic rate of fire. Depending on weapon type the accuracy and in extreme cases the muzzle velocity might be reduced on high temperatures. Adds smoke puff and heat refraction effects to indicate this.
### 1.3 Cookoff
Hot weapons can also cause chambered ammunition to spontaneously ignite. The higher the temperature of the weapon the sooner a cookoff can happen. Open bolt weapons (most machineguns) cannot cookoff unless jammed. Jammed weapons will not cookoff unless the jam is a failure to fire.
### 1.4 Spare barrels
Adds the ability to changes barrels on machine guns to compensate for those effects. Changing the barrel can also unjam the gun.
## 2. Usage
### 2.1 Clearing a jammed weapon
- To clear a jammed weapon, press <kbd>SHIFT</kbd> + <kbd>R</kbd> (ACE3 default key bind `Clear jam`).
### 2.2 Swapping barrels
- For this you need a `Spare barrel` and a compatible weapon.
- Press self interaction <kbd>Ctrl</kbd> + <kbd>&nbsp;Win</kbd> (ACE3 default key bind `Self Interaction Key`).
- Select `Equipment`.
- Select `Swap barrel`.
### 2.3 Checking your barrel temperature
- Press self interaction <kbd>Ctrl</kbd> + <kbd>&nbsp;Win</kbd>.
- Select `Equipment`.
- Select `Check weapon temperature`.
@ -43,6 +52,18 @@ Adds the ability to changes barrels on machine guns to compensate for those effe
**NOTE** When the bar is half full (yellow) it means the barrel is around 500°c.
Your weapon will be even more prone to jams, and it'll get worse if you don't let the barrel cool down or swap it.
### 2.4 Cooling your weapon
- Weapons and spare barrels will cool off over time.
- Cooling speed of weapons in increased in windy or rainy weather, and when swimming.
- If AceX Field Rations is loaded then weapons can be cooled with canteens, water bottles, or other beverage items. This does not require the Field Rations system to be enabled.
- If AceX Field Rations is enabled then weapons can also be cooled with the same water sources used to refill canteens and water bottles.
### 2.5 Avoiding cookoffs
- After a firefight unload closed bolt firearms (most rifles) until the barrel temperature has gone down to less than 180°C (two sections or less on the bar).
- Clear failure to fire jams quickly
## 3. Dependencies
{% include dependencies_list.md component="overheating" %}

View File

@ -27,7 +27,16 @@ class CfgWeapons {
};
```
### 1.2 Custom jam clearing animation
### 1.2 Custom jam types
```cpp
class CfgWeapons {
class Pistol_Base_F;
class MyRevolver : Pistol_Base_F {
ace_overheating_jamTypesAllowed = ["Fire","Dud"]; //Allowed and default values are ["Eject", "Extract", "Feed", "Fire", "Dud"]. In the example here a revolver does not eject, extract, or feed on each shot to those values are removed.
};
```
### 1.3 Custom jam clearing animation
```cpp
class CfgWeapons {
@ -36,3 +45,19 @@ class CfgWeapons {
};
};
```
### 1.4 Cook Off
```cpp
class CfgWeapons {
class Rifle_Long_Base_F ;
class MySniper : Rifle_Long_Base_F {
ace_overheating_closedBolt = 1; // Closed bolt, can cook off from barrel heat.
};
class MyMG : Rifle_Long_Base_F {
ace_overheating_closedBolt = 0; // Open bolt, can only cook off on failure to fire type jams.
};
};
```

View File

@ -107,6 +107,14 @@ class CfgWeapons {
ACE_barrelLength = 610.0;
};
class rhs_weap_Izh18 : Rifle_Base_F {
ace_overheating_jamTypesAllowed = ["Fire","Dud"];
};
class rhs_weap_m79 : Rifle_Base_F {
ace_overheating_jamTypesAllowed = ["Fire","Dud"];
};
CREATE_CSW_PROXY(rhs_weap_DSHKM);
class Launcher;

View File

@ -157,6 +157,9 @@ class CfgWeapons {
ACE_twistDirection = 0;
ACE_barrelLength = 508.0;
};
class rhs_weap_m32_Base_F : Rifle_Base_F {
ace_overheating_jamTypesAllowed = ["Fire","Dud"];
};
class SMG_02_base_F;
class rhsusf_weap_MP7A1_base_f: SMG_02_base_F {
ACE_barrelLength = 180;
@ -180,6 +183,10 @@ class CfgWeapons {
ACE_barrelTwist = 248.92;
ACE_barrelLength = 124.46;
};
class rhs_weap_M320_Base_F: Pistol_Base_F {
ace_overheating_jamTypesAllowed = ["Fire","Dud"];
};
// RHS sniper scopes
class ItemCore;
class InventoryOpticsItem_Base_F;