// Heavy weapons library include library.utils; include library.trace; include library.combat; include library.vehicle; include library.dot; include library.ai_lib; const int TYPE_LAUNCHER_PISTOL = 1; const int TYPE_ROCKET_LAUNCHER = 2; const int TYPE_FLAME_THROWER = 3; const int TYPE_HEAVY_PARTICLE_BEAM = 4; const int TYPE_ACID_RIFLE = 5; const int TYPE_HEAVY_ACID_BEAM = 6; const int TYPE_LIGHTNING_BEAM_CANON = 7; const int TYPE_PROTON_RIFLE = 8; const int TYPE_HEAVY_FLAME_THROWER = 9; const int TYPE_LAVA_CANNON = 10; //********************************************************************************************************* const int TYPE_MAX = 11; // this needs to get incremented if another weapon type is added //********************************************************************************************************* // Balance Knobs const float LAUNCHER_PISTOL_AOE_RADIUS = 10.0f; const float HVY_ACID_BEAM_AOE_RADIUS = 15.0f; const int HVY_ACID_BEAM_AOE_CHANCE = 25;// % const float HVY_ACID_BEAM_BLIND_DURATION = 20.0f; const float HVY_LIGHTNING_BEAM_AOE_RADIUS = 10.0f; const float FLAME_THROWER_CONE_ANGLE = 20.0f; const float HEAVY_FLAME_THROWER_CONE_ANGLE = 25.0f; const float LAVA_CANNON_AOE_RADIUS = 5.0f; const int FLAME_THROWER_DOT_CHANCE = 30;// % const float PARTICLE_BEAM_CONE_ANGLE = 1.0f; const int ACID_RIFLE_PUNISHMENT_CHANCE = 40;// % const float ACID_RIFLE_PUNISHMENT_DURATION = 15.0f; const int ACID_RIFLE_MID_BLIND_CHANCE = 40;//% const int ACID_RIFLE_MAX_BLIND_CHANCE = 20;//% const int LIGHTNING_BEAM_CANON_DOT_CHANCE = 20;//% const int LIGHTNING_BEAM_VEHICLE_SLOW_CHANCE = 100;//% const float LIGHTNING_BEAM_VEHICLE_SLOW_DURATION = 20.0f; // seconds const float LIGHTNING_BEAM_VEHICLE_SLOW_FACTOR = 0.1f; // 10% of normal speed const float FOCUS_FIRE_CONE_REDUCTION = .75f; // 75% smaller cone const float DEAD_BANG_RANGE_REDUCTION = .85f; // 85% range reduction const float DEMOLITION_DAM_MULTIPLIER = 1.4f; const float DEMOLITION_1_DAM_MULTIPLIER = 1.6f; const float DEMOLITION_2_DAM_MULTIPLIER = 1.8f; const string ATTACK_NAME_BASE_SINGLE = "co_hw_dot_"; const string ATTACK_NAME_BASE_AREA = "co_ae_hw_dot_"; // // returns the weapon type based on @weapon's template // int getHeavyWeaponType(obj_id weapon) { if(!hasObjVar(weapon, "intWeaponType")) { return -1; } int type = getIntObjVar(weapon, "intWeaponType"); if(type >= TYPE_MAX) { return -1; } return type; } // // fills the weapon_data class with the weapon data from the weapon object so we dont need to constantly query the weapon // boolean fillHeavyWeaponData(weapon_data dat) { int weaponType = getHeavyWeaponType(dat.id); if(weaponType == -1) { return false; } float AOEDamagePercent = 0.0f; if(hasObjVar(dat.id, "intAOEDamagePercent")) { AOEDamagePercent = getFloatObjVar(dat.id, "intAOEDamagePercent"); } if(AOEDamagePercent > 1.0f) { AOEDamagePercent = 1.0f; } else if(AOEDamagePercent < 0.0f) { AOEDamagePercent = 0.0f; } dat.hvyWeaponType = weaponType; dat.hvyWeaponAeDamageMod = AOEDamagePercent; return true; } // // Checks restrictions of firing heavy weapons. Currently only checks to ensure that your posture is // kneeling // boolean canFireHeavyWeapon(obj_id player, weapon_data weapon) { if (cybernetic.hasCommandoLegs( player )) return true;//with these legs, you don't have to kneel, so. // put posture restriction here, if any return true; } float getDemolitionShotDamageMod(obj_id attacker, obj_id defender, string actionName) { float mult = 1.05f; if(isPlayer(defender) || ai_lib.isNpc(defender) || ai_lib.isAnimal(defender)) { return mult; } actionName = toLower(actionName); if(actionName.equals("demolitionshot_2")) { mult = DEMOLITION_2_DAM_MULTIPLIER; } else if(actionName.equals("demolitionshot")) { mult = DEMOLITION_DAM_MULTIPLIER; } else if(actionName.equals("demolitionshot_1")) { mult = DEMOLITION_1_DAM_MULTIPLIER; } return mult; } // // constructs the target list for the heavy weapon, depending on the weapon. Does not include the // defender, as the defender is always an implicit target in the attack. The targeted defender needs // to be in the array at the first index. // obj_id[] getAllHeavyWeaponTargets(obj_id attacker, obj_id defender, weapon_data weapon, dictionary actionData) { float rangeModifier = 1f; float areaModifier = 1f; string actionName = toLower(actionData.getString("actionName")); if(actionName != null && actionName.length() > 0) { if(actionName.startsWith("demolition") || actionName.startsWith("overkill")) { return new obj_id[]{defender}; } if(actionName.startsWith("deadbang")) { rangeModifier = DEAD_BANG_RANGE_REDUCTION; } else if(actionName.startsWith("focusfire")) { areaModifier = FOCUS_FIRE_CONE_REDUCTION; } } obj_id[] defenders = null; switch(weapon.hvyWeaponType) { case TYPE_LAUNCHER_PISTOL: defenders = pvpGetTargetsInRange(attacker, defender, LAUNCHER_PISTOL_AOE_RADIUS * areaModifier); break; case TYPE_LAVA_CANNON: defenders = pvpGetTargetsInRange(attacker, defender, LAVA_CANNON_AOE_RADIUS * areaModifier); break; case TYPE_FLAME_THROWER: defenders = pvpGetTargetsInCone(attacker, attacker, defender, weapon.maxRange * rangeModifier, FLAME_THROWER_CONE_ANGLE * areaModifier); break; case TYPE_HEAVY_FLAME_THROWER: defenders = pvpGetTargetsInCone(attacker, attacker, defender, weapon.maxRange * rangeModifier, HEAVY_FLAME_THROWER_CONE_ANGLE * areaModifier); break; case TYPE_HEAVY_PARTICLE_BEAM: defenders = pvpGetTargetsInCone(attacker, attacker, defender, weapon.maxRange * rangeModifier, PARTICLE_BEAM_CONE_ANGLE * areaModifier); // switch the first and last elements so that the animation looks right (punch-through illusion) // assuming that the last element in the array is the furthest target if(defenders != null && defenders.length > 1) { obj_id temp = defenders[0]; defenders[0] = defenders[defenders.length-1]; defenders[defenders.length-1] = temp; } break; case TYPE_HEAVY_ACID_BEAM: defenders = pvpGetTargetsInRange(attacker, defender, HVY_ACID_BEAM_AOE_RADIUS * areaModifier); break; case TYPE_LIGHTNING_BEAM_CANON: defenders = pvpGetTargetsInRange(attacker, defender, HVY_LIGHTNING_BEAM_AOE_RADIUS * areaModifier); break; default: defenders = new obj_id[]{defender}; break; } return defenders; } // // This method is called by combat_heavy_weapon.script successful attack message handler as a result // of a successful heavy weapon attack. here we can perform any cleanup or perform any penalty actions // if necessary // void finalizeHeavyWeaponAttack(attacker_data attacker, defender_data[] defenders, weapon_data weapon) { effect_data dat = new effect_data(); defender_data defender = defenders[0]; switch(weapon.hvyWeaponType) { case TYPE_ACID_RIFLE: float distance = getDistance(attacker.id, defender.id); if(distance > (weapon.maxRange - weapon.minRange)/2) { return; } if(rand(1, 100) <= ACID_RIFLE_PUNISHMENT_CHANCE) { sendSystemMessage(attacker.id, new string_id("cbt_spam", "acid_rifle_selfblind")); } break; } return; } // // Gets called by the combat system after a hit @defender has been established and damage has been dealt. // This method will apply any special weapon effects. // effect_data doHeavyWeaponDefenderEffect(attacker_data attacker, defender_data defender, weapon_data weapon) { effect_data rslt = new effect_data(); switch(weapon.hvyWeaponType) { case TYPE_LAUNCHER_PISTOL: rslt = _doLauncherPistolAttack(attacker, defender, weapon); break; case TYPE_ROCKET_LAUNCHER: rslt = _doRocketLauncherAttack(attacker, defender, weapon); break; case TYPE_FLAME_THROWER: case TYPE_HEAVY_FLAME_THROWER: rslt = _doFlameThrowerAttack(attacker, defender, weapon); break; case TYPE_LAVA_CANNON: rslt = _doLavaCannonAttack(attacker, defender, weapon); break; case TYPE_HEAVY_PARTICLE_BEAM: rslt = _doHeavyParticleBeamAttack(attacker, defender, weapon); break; case TYPE_ACID_RIFLE: rslt = _doAcidRifleAttack(attacker, defender, weapon); break; case TYPE_HEAVY_ACID_BEAM: rslt = _doHeavyAcidBeamAttack(attacker, defender, weapon); break; case TYPE_LIGHTNING_BEAM_CANON: rslt = _doLightningBeamCanonAttack(attacker, defender, weapon); break; } return rslt; } ///////////////////////////////////////////////////////////////////////////////////// // For all of the _do* below functions, we are only applying the special effects, // that is, any effect that is not damage - damage will have already been applied // by the time these functions get called via the combat loops ///////////////////////////////////////////////////////////////////////////////////// effect_data _doLauncherPistolAttack(attacker_data attacker, defender_data defender, weapon_data weapon) { // launcher pistol currently only does AOE damage, which is handled in the core combat loop return new effect_data(); } effect_data _doLavaCannonAttack(attacker_data attacker, defender_data defender, weapon_data weapon) { // only does AOE damage, which is handled in the core combat loop return new effect_data(); } effect_data _doRocketLauncherAttack(attacker_data attacker, defender_data defender, weapon_data weapon) { effect_data dat = new effect_data(); dat.posture = POSTURE_KNOCKED_DOWN; return dat; } effect_data _doFlameThrowerAttack(attacker_data attacker, defender_data defender, weapon_data weapon) { if(rand(1, 100) <= FLAME_THROWER_DOT_CHANCE) { _applyHealthStatFireDot(attacker, defender, weapon); } return new effect_data(); } effect_data _doHeavyParticleBeamAttack(attacker_data attacker, defender_data defender, weapon_data weapon) { return new effect_data(); } effect_data _doAcidRifleAttack(attacker_data attacker, defender_data defender, weapon_data weapon) { effect_data dat = new effect_data(); dat.states = new int[]{STATE_BLINDED}; dat.durations = new float[]{ACID_RIFLE_PUNISHMENT_DURATION}; float distance = getDistance(defender.id, attacker.id); if(distance <= (weapon.maxRange-weapon.minRange)/2) { dat.stateChance = ACID_RIFLE_MID_BLIND_CHANCE; } else { dat.stateChance = ACID_RIFLE_MAX_BLIND_CHANCE; } return dat; } effect_data _doHeavyAcidBeamAttack(attacker_data attacker, defender_data defender, weapon_data weapon) { effect_data dat = new effect_data(); dat.states = new int[]{STATE_BLINDED}; dat.durations = new float[]{HVY_ACID_BEAM_BLIND_DURATION}; dat.stateChance = HVY_ACID_BEAM_AOE_CHANCE; return dat; } effect_data _doLightningBeamCanonAttack(attacker_data attacker, defender_data defender, weapon_data weapon) { if(rand(1, 100) <= LIGHTNING_BEAM_CANON_DOT_CHANCE) { _applyHealthStatFireDot(attacker, defender, weapon); if(vehicle.isRidingVehicle(defender.id) && rand(1, 100) <= LIGHTNING_BEAM_VEHICLE_SLOW_CHANCE) { obj_id objVehicle = getMountId(defender.id); vehicle.doTempMaxSpeedReduction(objVehicle, LIGHTNING_BEAM_VEHICLE_SLOW_FACTOR, LIGHTNING_BEAM_VEHICLE_SLOW_DURATION); sendSystemMessage(defender.id, new string_id("cbt_spam", "vehicle_slow_lightning")); } } return new effect_data(); } void _applyHealthStatFireDot(attacker_data attacker, defender_data defender, weapon_data weapon) { int duration = 0; int potency = 0; int strength = 0; switch(weapon.hvyWeaponType) { case TYPE_FLAME_THROWER: case TYPE_LIGHTNING_BEAM_CANON: { duration = rand(15, 45); potency = rand(125, 225); strength = rand(200, 400); } break; case TYPE_HEAVY_FLAME_THROWER: { duration = rand(15, 60); potency = rand(150, 275); strength = rand(300, 500); } break; } dot.applyDotEffect(defender.id, attacker.id, dot.DOT_FIRE, "hvyWeaponDot_" + HEALTH + "_" + attacker.id, HEALTH, potency, strength, duration, true, null); } string getHeavyWeaponDotName(obj_id player, obj_id weapon, boolean singleTarget) { int elementalDamageType = getWeaponElementalType(weapon); return getHeavyWeaponDotName(player, elementalDamageType, singleTarget); } string getHeavyWeaponDotName(obj_id player, weapon_data weaponData, boolean singleTarget) { int elementalDamageType = weaponData.elementalType; return getHeavyWeaponDotName(player, elementalDamageType, singleTarget); } string getHeavyWeaponDotName(obj_id player, int elementalDamageType, boolean singleTarget) { int playerLevel = getLevel(player); //bail out if there is no elemental weapon type if(elementalDamageType <= 0) return null; string attackNameBase= ""; string attackNameType = ""; string attackNameLevel = ""; string finalAttackName = ""; if (singleTarget) attackNameBase = ATTACK_NAME_BASE_SINGLE; else attackNameBase = ATTACK_NAME_BASE_AREA; switch(elementalDamageType) { case DAMAGE_ENERGY: attackNameType = "energy_"; break; case DAMAGE_KINETIC: attackNameType = "kinetic_"; break; case DAMAGE_ELEMENTAL_HEAT: attackNameType = "fire_"; break; case DAMAGE_ELEMENTAL_COLD: attackNameType = "cold_"; break; case DAMAGE_ELEMENTAL_ACID: attackNameType = "acid_"; break; case DAMAGE_ELEMENTAL_ELECTRICAL: attackNameType = "electrical_"; break; default: break; } if(playerLevel == 90) attackNameLevel = "5"; else if (playerLevel >= 80) attackNameLevel = "4"; else if (playerLevel >= 70) attackNameLevel = "3"; else if(playerLevel >= 60) attackNameLevel = "2"; else if(playerLevel >= 50) attackNameLevel = "1"; //string should look like this co_hw_dot_fire_5 = level 90 fire dot finalAttackName = attackNameBase + attackNameType + attackNameLevel; return finalAttackName; }