Files
dsrc/sku.0/sys.server/compiled/game/script/library/combat.scriptlib
T
2013-09-10 23:17:15 -07:00

4671 lines
145 KiB
Plaintext

include library.beast_lib;
include library.colors;
include library.weapons;
include library.utils;
include library.jedi;
include library.armor;
include library.heavyweapons;
include library.movement;
include library.buff;
include library.space_utils;
include library.group;
include library.static_item;
include library.prose;
// DO NOT UNCOMMENT THIS AND SUBMIT - IT'S FOR LOCAL TESTING ONLY!
//#define COMBATLOG 1;
// Regen multipliers
const float IN_COMBAT_REGEN_MULT = 1.0f;
const float OUT_COMBAT_REGEN_MULT = 4.0f;
const int PLAYER_COMBAT_BASE_DAMAGE = 60;
const int PLAYER_ATTACKER_DAMAGE_LEVEL_MULTIPLIER = 5;
// old weapon type defines
const int RANGED_WEAPON = -1;
const int MELEE_WEAPON = -2;
const int ALL_WEAPONS= -3;
const int ALL_LIGHTSABERS = -4;
const int FORCE_POWER = -5;
const int ALL_BUT_HEAVY = -6;
const int WEAPON_TYPE_FORCE_POWER = -1;
const int LEFT_CLICK_DEFAULT = 1;
const int RIGHT_CLICK_SPECIAL = 2;
// weapon type defines
//Bit values from command_table.tab:
//
// RIFLE=1,CARBINE=2, PISTOL=3,HEAVY=4, 1HAND_MELEE=5, 2HAND_MELEE=6, UNARMED=7, POLEARM=8, THROWN=9,
// 1HAND_LIGHTSABER=10, 2HAND_LIGHTSABER=11,POLEARM_LIGHTSABER=12, RANGED=28, MELEE=29, ALL=30,
// ALL_LIGHTSABERS=31, ALL_BUT_HEAVY=32
const int B_RIFLE= 0x00000001;
const int B_CARBINE= 0x00000002;
const int B_PISTOL= 0x00000004;
const int B_HEAVY= 0x00000008;
const int B_1HAND_MELEE= 0x00000010;
const int B_2HAND_MELEE= 0x00000020;
const int B_UNARMED= 0x00000040;
const int B_POLEARM= 0x00000080;
const int B_THROWN= 0x00000100;
const int B_1HAND_LIGHTSABER= 0x00000200;
const int B_2HAND_LIGHTSABER= 0x00000400;
const int B_POLEARM_LIGHTSABER= 0x00000800;
const int B_GROUND_TARGETTING= 0x00001000;
const int B_DIRECTIONAL= 0x00002000;
const int B_RANGED= 0x08000000;
const int B_MELEE= 0x10000000;
const int B_ALL= 0x20000000;
const int B_ALL_LIGHTSABERS= 0x40000000;
const int[] weaponTypeToBitValueMap =
{
B_ALL | B_RANGED | B_RIFLE, //WEAPON_TYPE_RIFLE
B_ALL | B_RANGED | B_CARBINE, //WEAPON_TYPE_LIGHT_RIFLE
B_ALL | B_RANGED | B_PISTOL, //WEAPON_TYPE_PISTOL
B_ALL | B_RANGED | B_HEAVY, //WEAPON_TYPE_HEAVY
B_ALL | B_MELEE | B_1HAND_MELEE, //WEAPON_TYPE_1HAND_MELEE
B_ALL | B_MELEE | B_2HAND_MELEE, //WEAPON_TYPE_2HAND_MELEE
B_ALL | B_MELEE | B_UNARMED, //WEAPON_TYPE_UNARMED
B_ALL | B_MELEE | B_POLEARM, //WEAPON_TYPE_POLEARM
B_ALL | B_RANGED | B_THROWN, //WEAPON_TYPE_THROWN
B_ALL | B_ALL_LIGHTSABERS | B_1HAND_LIGHTSABER, //WEAPON_TYPE_WT_1HAND_LIGHTSABER
B_ALL | B_ALL_LIGHTSABERS | B_2HAND_LIGHTSABER, //WEAPON_TYPE_WT_2HAND_LIGHTSABER
B_ALL | B_ALL_LIGHTSABERS | B_POLEARM_LIGHTSABER, //WEAPON_TYPE_WT_POLEARM_LIGHTSABER
B_ALL | B_RANGED | B_HEAVY | B_GROUND_TARGETTING, //WEAPON_TYPE_GROUND_TARGETTING
B_ALL | B_RANGED | B_HEAVY | B_DIRECTIONAL //WEAPON_TYPE_DIRECTIONAL
};
// ----------
const int EFFECT_MAX_DURATION = 180;
const float DELAY_EFFECT_TIMER = 30f;
const int TKA_JEDI_ARMOR_HARD_CAP = 9000;
const int MAX_RE_EXOTIC_ELEMENTAL_PENETRATION_SKILLMOD = 10;
// Max reuse time on snare/root
const int MOVEMENT_MODIFIER_REUSE_TIME = 30;
const int AI_LEVEL_ACCURACY_BONUS = 65;
const int AI_LEVEL_DEFENSE_BONUS = 65;
const int AI_LEVEL_DAMAGE_BONUS = 40;
const int AI_LEVEL_DAMAGE_PENALTY = 25;
const int AI_LEVEL_MIN_DAMAGE_CAP = 50;
const int AI_LEVEL_MAX_DAMAGE_CAP = 75;
const float AI_CON_SCALE_MULT = 8f;
//target validation defines
const int VALID_TARGET_NONE = -1;
const int VALID_TARGET_STANDARD = 0;
const int VALID_TARGET_MOB = 1;
const int VALID_TARGET_CREATURE = 2;
const int VALID_TARGET_NPC = 3;
const int VALID_TARGET_DROID = 4;
const int VALID_TARGET_PVP = 5;
const int VALID_TARGET_JEDI = 6;
const int VALID_TARGET_DEAD = 7;
const int VALID_TARGET_FRIEND = 8;
const int NON_ATTACK = 0;
const int NON_DAMAGE_ATTACK = 4;
const int HEAL = 5;
const int DELAY_ATTACK = 6;
const int REVIVE = 7;
const int ACTION_NAME = 0;
const int WEAPON_NAME = 1;
const int NO_ATTACK_NAME = 2;
const int NO_HIT_SPAM = 3;
// Armor break reduction
const float ARMOR_BREAK_REDUCTION = 0.5f;
// Cached Attacker Data
const string ATTACKER_DATA = "cached_attacker_data";
const string ATTACKER_BASE_CRIT = ATTACKER_DATA+".base_crit";
const string ATTACKER_PVP_CRIT = ATTACKER_DATA+".pvp_crit";
const string ATTACKER_DROID_CRIT = ATTACKER_DATA+".droid_crit";
const string ATTACKER_NPC_CRIT = ATTACKER_DATA+".npc_crit";
const string ATTACKER_CREATURE_CRIT = ATTACKER_DATA+".creature_crit";
const string ATTACKER_HIT_BONUS = ATTACKER_DATA+".hit_bonus";
const string ATTACKER_STRIKETHROUGH = ATTACKER_DATA+".strikethrough_chance";
const string ATTACKER_REDUCE_PARRY = ATTACKER_DATA+".reduce_parry";
const string ATTACKER_REDUCE_BLOCK = ATTACKER_DATA+".reudce_block";
const string ATTACKER_REDUCE_DODGE = ATTACKER_DATA+".reduce_dodge";
const string ATTACKER_REDUCE_GLANCING = ATTACKER_DATA+".reduce_glancing";
const string ATTACKER_REDUCE_EVADE = ATTACKER_DATA+".reduce_evade";
const string DEFENDER_DATA = "cached_defender_data";
const string DEFENDER_REDUCE_CRIT = DEFENDER_DATA+".crit_reduce";
const string DEFENDER_INCREASE_MISS = DEFENDER_DATA+".increase_miss";
const string DEFENDER_REDUCE_STRIKETHROUGH = DEFENDER_DATA+".reduce_strikethrough";
const string DEFENDER_REDUCE_PUNISH = DEFENDER_DATA+".reduce_punish";
const string DEFENDER_GLANCING = DEFENDER_DATA+".increase_glancing";
const string DEFENDER_PARRY = DEFENDER_DATA+".parry_chance";
const string DEFENDER_DODGE = DEFENDER_DATA+".dodge_chance";
const string DEFENDER_EVADE = DEFENDER_DATA+".evade_chance";
const string DEFENDER_BLOCK = DEFENDER_DATA+".block_chance";
// Armor slot hit location defines
const string[][] ARMOR_SLOT_LOCATIONS = {
{ "chest2", "chest2" }, //body
{ "hat", "hat" }, //head
{ "bicep_r", "bracer_upper_r" }, //right arm
{ "bicep_l", "bracer_upper_l" }, //left arm
{ "pants2", "pants2" }, //right leg
{ "pants2", "pants2" }//left leg
};
const string PSG_SLOT_LOCATION = "utility_belt";
// values used in the psg damage calculation
const float PSG_BASE_DMG_PER_HIT = 50.0f;
const float PSG_DAMAGE_SCALE = 200.0f;
const int CONE = 0;
const int SINGLE_TARGET= 1;
const int AREA = 2;
const int TARGET_AREA = 3; // area around target
const int DUAL_WIELD = 4; // 2 targets sometimes
const int RAMPAGE = 5;
const int RANDOM_HATE_TARGET = 6;
const int RANDOM_HATE_TARGET_CONE = 7;
const int RANDOM_HATE_TARGET_CONE_TERMINUS = 8;
const int HATE_LIST = 9;
const int RANDOM_HATE_MULTI = 10;
const int AREA_PROGRESSIVE = 11;
const int SPLIT_DAMAGE_TARGET_AREA = 12;
const int DISTANCE_FARTHEST = 13;
const string NONCOMBAT_DATATABLE = "datatables/combat/non_combat_data.iff";
const string WEAPON_LEVEL_TABLE = "datatables/combat/weapon_level.iff";
const int[] RANGED_WEAPONS = { WEAPON_TYPE_RIFLE, WEAPON_TYPE_LIGHT_RIFLE, WEAPON_TYPE_PISTOL, WEAPON_TYPE_HEAVY};
const int[] MELEE_WEAPONS = { WEAPON_TYPE_1HAND_MELEE,
WEAPON_TYPE_2HAND_MELEE,
WEAPON_TYPE_UNARMED,
WEAPON_TYPE_POLEARM,
WEAPON_TYPE_WT_1HAND_LIGHTSABER,
WEAPON_TYPE_WT_2HAND_LIGHTSABER,
WEAPON_TYPE_WT_POLEARM_LIGHTSABER };
const string VAR_VOLLEY_GROUPS = "squadleader.volleyGroups"; // IDs of the group that has marked a target
const string ID_VOLLEY_FIRE_PARTICLE = "squadleader.volleyFire"; // particle ID for volley effect
const int VOLLEY_INTERVAL = 30; // how often we can volley (~ equal to the cooldown)
const string VAR_GROUP_VOLLEY_TARGET = "squadleader.curVolleyTarget"; // the current volley target - used to turn off particles when player leaves group
const string VAR_GROUP_LAST_VOLLEY_TIME = "squadleader.lastVolleyTime"; // last time a SL has execute Volley fire in the group
const string_id SID_NONE = new string_id();
const string CHARGE_TARGET = "charge_target";
//scriptvars used to track recent death
const string DIED_RECENTLY = "combat.died_recently";
const float RECENT_DEATH_EXPIRATION = 60.0f;
const string DAMAGE_REDIRECT = "damage_redirect";
const string DO_ONCE_ATTACK_OVERRIDE = "default.do_once_attack_override";
const string DEFAULT_ATTACK_OVERRIDE = "default.default_attack_override";
//amount of hate to add to top hate for Jedi Taunt
const int FS_TAUNT_HATE_INCREASE_LOW = 750;
const int FS_TAUNT_HATE_INCREASE_MID = 1200;
const int FS_TAUNT_HATE_INCREASE_HIGH = 2000;
const int FS_TAUNT_LOW_LEVEL = 64;
const int FS_TAUNT_MID_LEVEL = 77;
//amount of hate to add to top hate for Bounty Hunter Taunt
const int BH_TAUNT_HATE_INCREASE_LOW = 300;
const int BH_TAUNT_HATE_INCREASE_MID = 750;
const int BH_TAUNT_HATE_INCREASE_HIGH = 1200;
const int BH_TAUNT_LOW_LEVEL = 43;
const int BH_TAUNT_MID_LEVEL = 66;
const string TEMP_COMBAT_BLOCK = "combat_debuff.all_actions_blocked";
const string PERSIST_COMBAT = "persist_combat";
const string BUFF_HATE_XFER = "aggroChannelself";
//egg aura buff constants
const int EGG_AURA_TRIGGER_MIN_RANGE = 15;
const int EGG_AURA_TRIGGER_MAX_RANGE = 100;
const int EGG_AURA_DURATION = 20;
const int DR_ATTACKER_CRITICAL = 0;
const int DR_ATTACKER_STRIKETHROUGH = 1;
const int DR_DEFENDER_BLOCK = 2;
const int DR_DEFENDER_DODGE = 3;
const int DR_DEFENDER_PARRY = 4;
const int DR_DEFENDER_GLANCE = 5;
const int DR_DEFENDER_EVADE = 6;
const int DR_ATTACKER_CRITICAL_2 = 7;
/**
* Send some combat spam, from an attack result.
*/
void sendCombatSpam (obj_id attacker, obj_id defender, obj_id weapon, combat_engine.hit_result result, string_id attackName, boolean sendToAttacker, boolean sendToDefender, boolean sendToBystanders)
{
sendCombatSpam(attacker, defender, weapon, result, attackName, sendToAttacker, sendToDefender, sendToBystanders, COMBAT_RESULT_GENERIC);
}
/**
* Send some combat spam, from an attack result.
*/
void sendCombatSpam (obj_id attacker, obj_id defender, string_id weapon, combat_engine.hit_result result, string_id attackName, boolean sendToAttacker, boolean sendToDefender, boolean sendToBystanders)
{
sendCombatSpam(attacker, defender, weapon, result, attackName, sendToAttacker, sendToDefender, sendToBystanders, COMBAT_RESULT_GENERIC);
}
/**
* Send a generic combat spam message to one player.
*/
void sendCombatSpamMessage(obj_id player, string_id message)
{
sendCombatSpamMessage(player, message, COMBAT_RESULT_GENERIC);
}
/**
* Send a generic combat spam message to multiple players.
*/
void sendCombatSpamMessage(obj_id attacker, obj_id defender, string_id message, boolean sendToAttacker, boolean sendToDefender, boolean sendToBystanders)
{
sendCombatSpamMessage(attacker, defender, message, sendToAttacker, sendToDefender, sendToBystanders, COMBAT_RESULT_GENERIC);
}
/**
* Send a generic combat spam prose package to one player.
*/
void sendCombatSpamMessageProse(obj_id player, prose_package pp)
{
sendCombatSpamMessageProse(player, pp, COMBAT_RESULT_GENERIC);
}
/**
* Send a generic combat spam prose package to multiple players.
*/
void sendCombatSpamMessageProse(obj_id attacker, obj_id defender, prose_package pp, boolean sendToAttacker, boolean sendToDefender, boolean sendToBystanders)
{
sendCombatSpamMessageProse(attacker, defender, pp, sendToAttacker, sendToDefender, sendToBystanders, COMBAT_RESULT_GENERIC);
}
void doBasicCombatSpam(string attackName, attacker_results attackerResults, defender_results[] defenderResults, hit_result[] hitData)
{
string_id attackId = new string_id("cmd_n", attackName);
doBasicCombatSpam(attackId, SID_NONE, null, attackerResults, defenderResults, hitData);
}
void doBasicCombatSpam(string_id attackId, string_id weaponId, obj_id weapon, attacker_results attackerResults, defender_results[] defenderResults, hit_result[] hitData)
{
if (isIdValid(weapon))
{
for (int i = 0; i < defenderResults.length; ++i)
sendCombatSpam(attackerResults.id, defenderResults[i].id, weapon, hitData[i], attackId, true, true, true, defenderResults[i].result);
}
else
{
for (int i = 0; i < defenderResults.length; ++i)
sendCombatSpam(attackerResults.id, defenderResults[i].id, weaponId, hitData[i], attackId, true, true, true, defenderResults[i].result);
}
}
boolean isAreaAttack(int attackType)
{
if(attackType == CONE || attackType == AREA || attackType == TARGET_AREA)
return true;
return false;
}
boolean breakMez(obj_id defender)
{
int[] buffs = buff.getAllBuffs(defender);
if(buffs == null || buffs.length == 0)
return true;
for(int i = 0; i < buffs.length; i++)
{
for(int j = 1; j <= buff.MAX_EFFECTS; j++)
{
string effect = buff.getEffectParam(buffs[i], j);
if(effect.equals("delay_attack"))
buff.removeBuff(defender, buffs[i]);
}
}
return true;
}
int getAiLevelDiff(obj_id attacker, obj_id defender)
{
obj_id master = null;
// Players don't get adjustments when attacking
if (isPlayer(attacker))
return 0;
// No level difference for inanimate objects
if (!isPlayer(defender) && !isMob(defender))
return 0;
// Pets don't get bonuses when attacking
if (isMob(attacker))
{
master = getMaster(attacker);
if (!isPlayer(attacker) && isIdValid(master))
return 0;
}
// Only pets get adjusted when defending.
if (isMob(defender))
{
master = getMaster(defender);
if (!isPlayer(defender) && !isIdValid(master))
return 0;
}
int aiLevel = getLevel(attacker);
int defenderLevel = getLevel(defender);
if (!isMob(attacker))
{
aiLevel = getIntObjVar(attacker, "intCombatDifficulty");
}
obj_id defenderGroup = getGroupObject(defender);
if (group.isGroupObject(defenderGroup))
defenderLevel = getGroupObjectLevel(defenderGroup);
int buffBonus = getEnhancedSkillStatisticModifier(defender, "private_relative_level_mod");
defenderLevel += buffBonus;
return (aiLevel - defenderLevel);
}
float getConBalanceScalar(int levelDiff)
{
float scale = (Math.abs(levelDiff)+AI_CON_SCALE_MULT)/(10f+AI_CON_SCALE_MULT);
return scale;
}
int getWeaponDamage(obj_id objAttacker, obj_id objDefender, weapon_data cbtWeaponData, int intAttackerRoll, int intDefenderRoll)
{
int intFinalDamage = 0;
float fltMinDamage = (float)cbtWeaponData.minDamage;
float fltMaxDamage= (float)cbtWeaponData.maxDamage;
if(!isPlayer(objAttacker))
{
if(intAttackerRoll> intDefenderRoll)
{
return getAiDamage(objAttacker, objDefender, fltMinDamage, fltMaxDamage);
}
else
{
return (int)rand(fltMinDamage, fltMinDamage+(fltMaxDamage - fltMinDamage));
}
}
if(intAttackerRoll> intDefenderRoll)
{
int intDamage = (int)rand(fltMinDamage+(fltMaxDamage - fltMinDamage), fltMaxDamage);
return intDamage;
}
else
{
int intDamage = (int)(rand(fltMinDamage, fltMinDamage+(fltMaxDamage - fltMinDamage)));
return intDamage;
}
}
int getAiDamage(obj_id attacker, obj_id defender, float minDamage, float maxDamage)
{
// Ai vs Player damage scaling
if (!isPlayer(attacker))
{
int levelDiff = getAiLevelDiff(attacker, defender);
combatLog(attacker, defender, "modifyAiRawDamage", "AI Level diff = "+levelDiff);
float levelDiffMod = levelDiff * getConBalanceScalar(levelDiff);
if (levelDiffMod < 0)
{
float damageMod = 1f + (levelDiffMod / 10f);
combatLog(attacker, defender, "modifyAiRawDamage", "AI Damage Mod = "+damageMod);
float minCap = minDamage / 2f;
float maxCap = maxDamage / 2f;
combatLog(attacker, defender, "modifyAiRawDamage", "Min Cap = "+minCap);
combatLog(attacker, defender, "modifyAiRawDamage", "Max Cap = "+maxCap);
minDamage *= damageMod;
maxDamage *= damageMod;
if (minDamage < minCap) minDamage = minCap;
if (maxDamage < maxCap) maxDamage = maxCap;
}
else
{
float maxDmgMod = 1f + (levelDiffMod / 10f);
combatLog(attacker, defender, "modifyAiRawDamage", "AI Max Damage Mod = "+maxDmgMod);
maxDamage *= maxDmgMod;
maxDamage += levelDiff;
float minDmgMod = 1f + (levelDiffMod / (10f / levelDiff));
combatLog(attacker, defender, "modifyAiRawDamage", "AI Min Damage Mod = "+minDmgMod);
minDamage *= minDmgMod;
float minCap = maxDamage / 2f;
if (minDamage > minCap) minDamage = minCap;
}
combatLog(attacker, defender, "modifyAiRawDamage", "New Min Damage = "+minDamage);
combatLog(attacker, defender, "modifyAiRawDamage", "New Max Damage = "+maxDamage);
}
if(utils.checkConfigFlag("ScriptFlags","e3Demo"))
{
if(isPlayer(defender))
{
int intCurrentHP = getHealth(defender);
int intMaxHP = getMaxHealth(defender);
if((minDamage>intMaxHP)||(maxDamage>intMaxHP)||(intCurrentHP< (intMaxHP/2)))
{
minDamage = 1;
maxDamage = 5;
}
}
}
int intDamage = (int)rand(minDamage, maxDamage);
return intDamage;
}
void assignDamageCredit(obj_id attacker, obj_id defender, weapon_data weaponData, int damage)
{
string xpType = xp.getWeaponXpType(weaponData.weaponType);
if (!isPlayer(attacker))
{
// Check to see if this is a CH pet
if (pet_lib.isPet(attacker))
{
obj_id master = getMaster(attacker);
if (ai_lib.isMonster(attacker) && isIdValid(master) && hasSkill(master, "outdoors_creaturehandler_novice"))
{
// Assign CH xp credit to the master
xp.updateCombatXpList(defender, master, xp.CREATUREHANDLER, damage);
// Assign training xp credit to pet
xp.updateCombatXpList(defender, attacker, xp.CREATUREHANDLER, damage);
return;
}
else if (isIdValid(master) && !beast_lib.isBeast(attacker))
{
// Assign pet xp to master (for master XP reductions)
xp.updateCombatXpList(defender, master, xp.PET_DAMAGE, damage);
// Also assign npc credit to pet for kill rights
xp.updateCombatXpList(defender, attacker, xpType, damage);
return;
}
}
}
xp.updateCombatXpList(defender, attacker, xpType, damage);
}
// Expertise chance to cast a buff/debuff
boolean expertiseRandomBuffChance(obj_id attacker, combat_data actionData)
{
string specialLine = actionData.specialLine;
if(getSkillStatisticModifier(attacker, "expertise_use_buff_chance_line_" + specialLine) > 0 ||
getSkillStatisticModifier(attacker, "private_use_buff_chance_line_" + specialLine) > 0)
{
// Get the action's specialLine value, which is a percentage chance to cast.
int chance = getSkillStatisticModifier(attacker, "expertise_buff_chance_line_" + specialLine );
// Negative means that it cannot be cast.
// Most buffs do not have an expertise associated with them and have no chance assigned.
// Those buffs should always cast, if they're 0. If the chance is set, then check it.
if(rand(0,99) > chance)
{
return false;
}
}
return true;
}
boolean applyDefenderCombatBuffs(obj_id attacker, obj_id defender, weapon_data weaponData, combat_data actionData)
{
string actionName = actionData.actionName;
string buffName = actionData.buffNameTarget;
float buffStrength = actionData.buffStrengthTarget;
float buffDuration = actionData.buffDurationTarget;
if (buffName == null || buffName.equals(""))
return false;
if(!expertiseRandomBuffChance(attacker, actionData))
return false;
combatLog(attacker, defender, "applyDefenderCombatBuffs", "Applying defender buff - " + buffName);
//if this is a dot type buff, we need to check for resistance.
//later this data will be in the action data, for now we have to use the less effiencent way
string effectName = buff.getEffectParam(buffName, 2);
if(effectName.equals("dot"))
{
effectName = buff.getEffectParam(buffName, 1);
string type = effectName.substring((effectName.lastIndexOf("_") + 1), effectName.length());
if (dot.attemptDotResist(defender, type, 100, true))
{
return false;
}
if(!dot.canApplyDotType(defender, type))
{
return false;
}
}
buffDuration += getExpertiseBuffDurationMods(attacker, actionData, buffName, buffDuration);
return buff.applyBuff(defender, attacker, buffName, buffDuration, buffStrength);
}
boolean applyAttackerCombatBuffs(obj_id attacker, combat_data actionData)
{
string actionName = actionData.actionName;
string buffName = actionData.buffNameSelf;
float buffStrength = actionData.buffStrengthSelf;
float buffDuration = actionData.buffDurationSelf;
if (buffName == null || buffName.equals(""))
return false;
if(!expertiseRandomBuffChance(attacker, actionData))
return false;
combatLog(attacker, null, "applyAttackerCombatBuffs", "Applying attacker buff - "+buffName);
buffDuration += getExpertiseBuffDurationMods(attacker, actionData, buffName, buffDuration);
return buff.applyBuff(attacker, attacker, buffName, buffDuration, buffStrength);
}
float getExpertiseBuffDurationMods(obj_id buffCaster, combat_data actionData, string buffName, float buffDuration)
{
float expertiseBuffDurationMod = 0.0f;
dictionary dic = dataTableGetRow(buff.BUFF_TABLE, buffName);
if (dic == null)
return expertiseBuffDurationMod;
string buffGroupName = dic.getString("GROUP1");
if(buffDuration == 0)
buffDuration = dic.getFloat("DURATION");
string specialLine = actionData.specialLine;
expertiseBuffDurationMod = buffDuration + getEnhancedSkillStatisticModifierUncapped(buffCaster, "expertise_buff_duration_line_" + specialLine );
expertiseBuffDurationMod += getEnhancedSkillStatisticModifierUncapped(buffCaster, "expertise_buff_duration_group_" + toLower(buffGroupName) );
expertiseBuffDurationMod += getEnhancedSkillStatisticModifierUncapped(buffCaster, "expertise_buff_duration_single_" + toLower(buffName) );
return expertiseBuffDurationMod;
}
boolean applyCombatMovementModifier(obj_id attacker, obj_id defender, string actionName)
{
boolean isRoot = movement.isRoot(actionName);
boolean isSnare = movement.isSnare(actionName);
boolean isStun = movement.isStunEffect(actionName);
prose_package pp = new prose_package();
string movementType = "";
if (isRoot) movementType = "root";
if (isSnare) movementType = "snare";
int lastMovementMod;
if (isPlayer(defender))
lastMovementMod = utils.getIntScriptVar(defender, "combat.movement."+movementType+".time");
else
lastMovementMod = utils.getIntScriptVar(defender, "combat.movement."+movementType+"."+attacker+".time");
if (lastMovementMod + MOVEMENT_MODIFIER_REUSE_TIME > getGameTime())
{
showCombatText(defender, attacker, new string_id("combat_effects", "no_effect"), 1.5f, colors.WHITE);
return false;
}
if (buff.hasBuff(defender, actionName) || !buff.canApplyBuff(defender, actionName))
{
showCombatText(defender, attacker, new string_id("combat_effects", "no_effect"), 1.5f, colors.WHITE);
return false;
}
if (isStun)
{
pp = prose.setStringId(pp, new string_id("combat_effects", "go_stunned"));
showFlyTextPrivateProseWithFlags(defender, defender, pp, 1.5f, colors.LAWNGREEN, FLY_TEXT_FLAG_IS_SNARE );
playClientEffectObj(defender, "appearance/pt_state_cantmove.prt", defender, "", null, "state_stunned");
playClientEffectObj(defender, "sound/sta_rooted_on.snd", defender, "");
}
else if (isRoot)
{
pp = prose.setStringId(pp, new string_id("combat_effects", "go_rooted"));
showFlyTextPrivateProseWithFlags(defender, defender, pp, 1.5f, colors.LAWNGREEN, FLY_TEXT_FLAG_IS_SNARE );
playClientEffectObj(defender, "appearance/pt_state_cantmove.prt", defender, "", null, "state_rooted");
playClientEffectObj(defender, "sound/sta_rooted_on.snd", defender, "");
}
else if (isSnare)
{
pp = prose.setStringId(pp, new string_id("combat_effects", "go_snare"));
showFlyTextPrivateProseWithFlags(defender, defender, pp, 1.5f, colors.LAWNGREEN, FLY_TEXT_FLAG_IS_SNARE );
playClientEffectObj(defender, "appearance/pt_state_slowedmove.prt", defender, "", null, "state_snared");
playClientEffectObj(defender, "sound/sta_snared_on.snd", defender, "");
}
if (isPlayer(defender))
utils.setScriptVar(defender, "combat.movement."+movementType+".time", getGameTime());
else
utils.setScriptVar(defender, "combat.movement."+movementType+"."+attacker+".time", getGameTime());
return true;
}
void removeCombatMovementModifierEffect(obj_id self, string buffName)
{
if(buffName == null || buffName.equals(""))
return;
boolean isVisible = true;
dictionary buffData = dataTableGetRow("datatables/buff/buff.iff", buffName);
if ( buffData != null )
isVisible = buffData.getInt("VISIBLE")==1;
prose_package pp = new prose_package();
if (movement.isStunEffect(buffName))
{
if ( isVisible )
{
pp = prose.setStringId(pp, new string_id("combat_effects", "no_stunned"));
showFlyTextPrivateProseWithFlags(self, self, pp, 1.5f, colors.TOMATO, FLY_TEXT_FLAG_IS_SNARE );
}
stopClientEffectObjByLabel(self, "state_stunned");
playClientEffectObj(self, "sound/sta_rooted_off.snd", self, "");
}
else if (movement.isRoot(buffName))
{
if ( isVisible )
{
pp = prose.setStringId(pp, new string_id("combat_effects", "no_rooted"));
showFlyTextPrivateProseWithFlags(self, self, pp, 1.5f, colors.TOMATO, FLY_TEXT_FLAG_IS_SNARE );
}
stopClientEffectObjByLabel(self, "state_rooted");
playClientEffectObj(self, "sound/sta_rooted_off.snd", self, "");
}
else if (movement.isSnare(buffName) && buff.getDuration(buffName) > 0f)
{
if ( isVisible )
{
pp = prose.setStringId(pp, new string_id("combat_effects", "no_snare"));
showFlyTextPrivateProseWithFlags(self, self, pp, 1.5f, colors.TOMATO, FLY_TEXT_FLAG_IS_SNARE );
}
stopClientEffectObjByLabel(self, "state_snared");
playClientEffectObj(self, "sound/sta_snared_off.snd", self, "");
}
}
void removeCombatBuffEffect(obj_id self, string buffName)
{
for (int i = 1; i <= buff.MAX_EFFECTS; i++)
{
int effectType = getStringCrc(buff.getEffectParam(buffName, i));
float effectValue = buff.getEffectValue(buffName, i);
if (effectType == ##"combat_slow")
{
stopClientEffectObjByLabel(self, "state_slowattack");
playClientEffectObj(self, "sound/sta_slow_off.snd", self, "");
}
}
}
boolean gainCombatActionAttribute(obj_id self, int action)
{
if(getMaxAction(self) == getAction(self))
return false;
int newAction = getAction(self) + action;
if(newAction > getMaxAction(self))
newAction = getMaxAction(self);
setAction(self, newAction);
return true;
}
boolean drainCombatActionAttributes(obj_id self, int[] actionCost)
{
if(!(drainAttributes(self, actionCost[1], actionCost[2])))
return false;
return true;
}
boolean canDrainCombatActionAttributes(obj_id self, int actionCost)
{
int[] tempArray = {0, actionCost, 0};
return canDrainCombatActionAttributes(self, tempArray);
}
boolean canDrainCombatActionAttributes(obj_id self, int[] actionCost)
{
if(actionCost[1] > 0)
{
if(testDrainAttribute(self, ACTION, actionCost[1]) < 0)
{
// combatLog(self, self, "canDrainCombatActionAttributes", "Not enough Action points");
return false;
}
}
return true;
}
// Return values for canPerformAction() - Used only by AI at the moment
const int ACTION_SUCCESS = 0;
const int ACTION_INVALID_DATA = 1;
const int ACTION_INVALID_WEAPON = 2;
const int ACTION_TOO_TIRED = 3;
int canPerformAction(string actionName, obj_id self)
{
actionName = getBestAction(self, actionName);
// Load action data from combat datatable
combat_data actionData = combat_engine.getCombatData(actionName);
// No valid action data found
if (actionData == null)
return ACTION_INVALID_DATA;
// Load the weapon data
weapon_data weaponData = new weapon_data();
weaponData = weapons.getNewWeaponData(getCurrentWeapon(self));
// Validate that this action can be used with this weapon
if (!canUseWeaponWithAbility(self, weaponData, actionData, true))
return ACTION_INVALID_WEAPON;
// Determine action cost and validate
int[] actionCost = getActionCost(self, weaponData, actionData);
if (!canDrainCombatActionAttributes(self, actionCost))
return ACTION_TOO_TIRED;
return ACTION_SUCCESS;
}
boolean isCommandoBonus(obj_id self, weapon_data weaponData, int commandType)
{
return isPlayer(self) && utils.isProfession(self, utils.COMMANDO) && isHeavyWeapon(weaponData) && (commandType == LEFT_CLICK_DEFAULT);
}
int[] getActionCost(obj_id self, weapon_data weaponData, dictionary actionData)
{
int[] cost = new int[3];
float healthCost=0;
float actionCost;
float mindCost;
// Get base ability costs
actionCost = actionData.getFloat("actionCost");
if(isCommandoBonus(self, weaponData, actionData.getInt("commandType")))
{
cost[0] = (int)(0);
cost[1] = (int)(0);
cost[2] = (int)(0);
return cost;
}
// Modify action cost for expertise
string specialLine = actionData.getString("specialLine");
string actionName = actionData.getString("actionName");
int freeshotChance = getEnhancedSkillStatisticModifierUncapped(self, "expertise_freeshot_" + specialLine);
prose_package pp = new prose_package();
if(freeshotChance > 0)
{
int roll = rand(1, 100) + freeshotChance;
if(roll > 100)
{
actionCost = 0;
pp = prose.setStringId(pp, new string_id("spam", "freeshot"));
showFlyTextPrivateProseWithFlags(self, self, pp, 1.5f, colors.LEMONCHIFFON, FLY_TEXT_FLAG_IS_FREESHOT );
pp = prose.setStringId(pp, new string_id("cbt_spam", "freeshot"));
pp = prose.setTU(pp, self);
sendCombatSpamMessageProse(self, self, pp, true, true, true, COMBAT_RESULT_GENERIC);
cost[0] = (int)(0);
cost[1] = (int)(0);
cost[2] = (int)(0);
return cost;
}
}
if(actionCost > 0)
{
float expertiseActionCostMod = 0;
float percentAddFromWeapon = actionData.getFloat("percentAddFromWeapon");
if(percentAddFromWeapon > 0.0f)
{
expertiseActionCostMod += getEnhancedSkillStatisticModifierUncapped(self, "expertise_action_weapon_" + weaponData.weaponType);
if(isMeleeWeapon(weaponData.id))
{
expertiseActionCostMod += getEnhancedSkillStatisticModifierUncapped(self, "expertise_action_weapon_melee");
}
if (isRangedWeapon(weaponData.id))
{
expertiseActionCostMod += getEnhancedSkillStatisticModifierUncapped(self, "expertise_action_weapon_ranged");
}
}
expertiseActionCostMod += getEnhancedSkillStatisticModifierUncapped(self, "expertise_action_all");
if(specialLine != null && specialLine != "")
expertiseActionCostMod += getEnhancedSkillStatisticModifierUncapped(self, "expertise_action_line_" + specialLine);
if(actionName != null && actionName != "")
expertiseActionCostMod += getEnhancedSkillStatisticModifierUncapped(self, "expertise_action_single_" + actionName);
// Weapon stat bonuses
switch(weaponData.weaponType)
{
case WEAPON_TYPE_PISTOL:
expertiseActionCostMod += (float)getEnhancedSkillStatisticModifierUncapped(self, "expertise_action_pistol");
break;
case WEAPON_TYPE_RIFLE:
expertiseActionCostMod += (float)getEnhancedSkillStatisticModifierUncapped(self, "expertise_action_rifle");
break;
case WEAPON_TYPE_LIGHT_RIFLE:
expertiseActionCostMod += (float)getEnhancedSkillStatisticModifierUncapped(self, "expertise_action_carbine");
break;
case WEAPON_TYPE_HEAVY:
expertiseActionCostMod += (float)getEnhancedSkillStatisticModifierUncapped(self, "expertise_action_heavy");
break;
case WEAPON_TYPE_GROUND_TARGETTING:
expertiseActionCostMod += (float)getEnhancedSkillStatisticModifierUncapped(self, "expertise_action_heavy");
break;
case WEAPON_TYPE_DIRECTIONAL:
expertiseActionCostMod += (float)getEnhancedSkillStatisticModifierUncapped(self, "expertise_action_heavy");
break;
case WEAPON_TYPE_1HAND_MELEE:
expertiseActionCostMod += (float)getEnhancedSkillStatisticModifierUncapped(self, "expertise_action_1h");
break;
case WEAPON_TYPE_2HAND_MELEE:
expertiseActionCostMod += (float)getEnhancedSkillStatisticModifierUncapped(self, "expertise_action_2h");
break;
case WEAPON_TYPE_UNARMED:
expertiseActionCostMod += (float)getEnhancedSkillStatisticModifierUncapped(self, "expertise_action_unarmed");
break;
case WEAPON_TYPE_POLEARM:
expertiseActionCostMod += (float)getEnhancedSkillStatisticModifierUncapped(self, "expertise_action_polearm");
break;
case WEAPON_TYPE_WT_1HAND_LIGHTSABER:
case WEAPON_TYPE_WT_2HAND_LIGHTSABER:
case WEAPON_TYPE_WT_POLEARM_LIGHTSABER:
expertiseActionCostMod += (float)getEnhancedSkillStatisticModifierUncapped(self, "expertise_action_lightsaber");
break;
}
actionCost = actionCost - (actionCost * (expertiseActionCostMod/100.0f) );
if(actionCost < 0)
actionCost = 0;
}
// A negative number means cost is a percent of total action.
if(actionCost < 0)
{
float expertiseActionCostMod = 0;
// Use the unmodified action max
// The datatable had bad values. This is a close estimate.
int actionTotal = getLevel(self) * 100;
actionCost = actionTotal * (float)(-1.0f * ((float)actionCost / 100.0f));
//In GU10, fixed Abilities that cost a percentage not using the generic expertise_action_all modifier.
expertiseActionCostMod += getEnhancedSkillStatisticModifierUncapped(self, "expertise_action_all");
if(specialLine != null && specialLine != "")
expertiseActionCostMod += getEnhancedSkillStatisticModifierUncapped(self, "expertise_action_line_" + specialLine);
if(actionName != null && actionName != "")
expertiseActionCostMod += getEnhancedSkillStatisticModifierUncapped(self, "expertise_action_single_" + actionName);
actionCost = actionCost * (1 - (expertiseActionCostMod/100));
}
// Action Burn: Lose health based on action used.
if(utils.hasScriptVar(self, "buff.action_burn.value") && actionCost > 0)
{
int rightNow = getGameTime();
int timestamp = utils.getIntScriptVar(self, "buff.action_burn.timestamp");
if(rightNow > timestamp)
{
utils.setScriptVar(self, "buff.action_burn.timestamp", rightNow);
float burnRatio = utils.getFloatScriptVar(self, "buff.action_burn.value");
float actionBurn = (float)actionCost * (burnRatio/100);
int currentHealth = getHealth(self);
int modifiedHealth = currentHealth - (int)actionBurn;
if(modifiedHealth < 1)
{
modifiedHealth = 1;
}
int[] burnBuffs = buff.getAllBuffsByEffect(self, "action_burn");
obj_id burnBuffOwner = self;
if(burnBuffs.length > 0 && utils.hasScriptVar(self, "buffOwner." + burnBuffs[0]))
{
burnBuffOwner = utils.getObjIdScriptVar(self, "buffOwner." + burnBuffs[0]);
}
setHealth(self, modifiedHealth);
pp = prose.setStringId(pp, new string_id("spam", "crippling_pain"));
showFlyTextPrivateProseWithFlags(self, burnBuffOwner, pp, 1.5f, colors.ORANGERED, FLY_TEXT_FLAG_IS_CRITICAL_HIT);
pp = prose.setStringId(pp, new string_id("cbt_spam", "crippling_pain"));
pp = prose.setTU(pp, self);
pp = prose.setDI(pp, (int)actionBurn);
sendCombatSpamMessageProse(self, burnBuffOwner, pp, true, true, true, COMBAT_RESULT_DEBUFF);
}
}
// Modify by weapon costs
//healthCost *= ((float)weaponData.attackCost / 100f);
//actionCost *= (float)weaponData.attackCost;
//mindCost *= (float)weaponData.attackCost;
cost[0] = (int)(0);
cost[1] = (int)(actionCost);
cost[2] = (int)(0);
// combatLog(self, null, "getActionCost", "Final Action cost = ["+cost[0]+", "+cost[1]+", "+cost[2]+"]");
return cost;
}
int[] getActionCost(obj_id self, weapon_data weaponData, combat_data actionData)
{
int[] cost = new int[3];
float healthCost=0;
float actionCost;
float mindCost;
// Get base ability costs
actionCost = actionData.actionCost;
if(isCommandoBonus(self, weaponData, actionData.commandType))
{
cost[0] = (int)(0);
cost[1] = (int)(0);
cost[2] = (int)(0);
return cost;
}
if(beast_lib.isBeast(self))
{
cost[0] = 0;
cost[1] = (int)actionData.vigorCost;
cost[2] = 0;
return cost;
}
// Modify action cost for expertise
string specialLine = actionData.specialLine;
string actionName = actionData.actionName;
int freeshotChance = getEnhancedSkillStatisticModifierUncapped(self, "expertise_freeshot_" + specialLine);
prose_package pp = new prose_package();
if(freeshotChance > 0)
{
int roll = rand(1, 100) + freeshotChance;
if(roll > 100)
{
actionCost = 0;
pp = prose.setStringId(pp, new string_id("spam", "freeshot"));
showFlyTextPrivateProseWithFlags(self, self, pp, 1.5f, colors.LEMONCHIFFON, FLY_TEXT_FLAG_IS_FREESHOT );
pp = prose.setStringId(pp, new string_id("cbt_spam", "freeshot"));
pp = prose.setTU(pp, self);
sendCombatSpamMessageProse(self, self, pp, true, true, true, COMBAT_RESULT_GENERIC);
cost[0] = (int)(0);
cost[1] = (int)(0);
cost[2] = (int)(0);
return cost;
}
}
if(actionCost > 0)
{
float expertiseActionCostMod = 0;
if(actionData.percentAddFromWeapon > 0.0f)
{
expertiseActionCostMod += getEnhancedSkillStatisticModifierUncapped(self, "expertise_action_weapon_" + weaponData.weaponType);
if(isMeleeWeapon(weaponData.id))
{
expertiseActionCostMod += getEnhancedSkillStatisticModifierUncapped(self, "expertise_action_weapon_melee");
}
if (isRangedWeapon(weaponData.id))
{
expertiseActionCostMod += getEnhancedSkillStatisticModifierUncapped(self, "expertise_action_weapon_ranged");
}
}
//Exotic Action Reduction Healing Modifier Bits
if(actionData.hitType == HEAL)
{
expertiseActionCostMod += getEnhancedSkillStatisticModifierUncapped(self, "exotic_heal_action_reduction");
}
expertiseActionCostMod += getEnhancedSkillStatisticModifierUncapped(self, "expertise_action_all");
if(specialLine != null && specialLine != "")
expertiseActionCostMod += getEnhancedSkillStatisticModifierUncapped(self, "expertise_action_line_" + specialLine);
if(actionName != null && actionName != "")
expertiseActionCostMod += getEnhancedSkillStatisticModifierUncapped(self, "expertise_action_single_" + actionName);
// Weapon stat bonuses
switch(weaponData.weaponType)
{
case WEAPON_TYPE_PISTOL:
expertiseActionCostMod += (float)getEnhancedSkillStatisticModifierUncapped(self, "expertise_action_pistol");
break;
case WEAPON_TYPE_RIFLE:
expertiseActionCostMod += (float)getEnhancedSkillStatisticModifierUncapped(self, "expertise_action_rifle");
break;
case WEAPON_TYPE_LIGHT_RIFLE:
expertiseActionCostMod += (float)getEnhancedSkillStatisticModifierUncapped(self, "expertise_action_carbine");
break;
case WEAPON_TYPE_HEAVY:
expertiseActionCostMod += (float)getEnhancedSkillStatisticModifierUncapped(self, "expertise_action_heavy");
break;
case WEAPON_TYPE_GROUND_TARGETTING:
expertiseActionCostMod += (float)getEnhancedSkillStatisticModifierUncapped(self, "expertise_action_heavy");
break;
case WEAPON_TYPE_DIRECTIONAL:
expertiseActionCostMod += (float)getEnhancedSkillStatisticModifierUncapped(self, "expertise_action_heavy");
break;
case WEAPON_TYPE_1HAND_MELEE:
expertiseActionCostMod += (float)getEnhancedSkillStatisticModifierUncapped(self, "expertise_action_1h");
break;
case WEAPON_TYPE_2HAND_MELEE:
expertiseActionCostMod += (float)getEnhancedSkillStatisticModifierUncapped(self, "expertise_action_2h");
break;
case WEAPON_TYPE_UNARMED:
expertiseActionCostMod += (float)getEnhancedSkillStatisticModifierUncapped(self, "expertise_action_unarmed");
break;
case WEAPON_TYPE_POLEARM:
expertiseActionCostMod += (float)getEnhancedSkillStatisticModifierUncapped(self, "expertise_action_polearm");
break;
case WEAPON_TYPE_WT_1HAND_LIGHTSABER:
case WEAPON_TYPE_WT_2HAND_LIGHTSABER:
case WEAPON_TYPE_WT_POLEARM_LIGHTSABER:
expertiseActionCostMod += (float)getEnhancedSkillStatisticModifierUncapped(self, "expertise_action_lightsaber");
break;
}
actionCost = actionCost - (actionCost * (expertiseActionCostMod/100.0f) );
if(actionCost < 0)
actionCost = 0;
}
// Cost is a percent of total action.
if(actionCost < 0)
{
float expertiseActionCostMod = 0;
//int actionTotal = getMaxAction(self);
// Use the unmodified action max
// The datatable had bad values. This is a close estimate.
int actionTotal = getLevel(self) * 100;
actionCost = actionTotal * (float)(-1.0f * ((float)actionCost / 100.0f));
//In GU10, fixed Abilities that cost a percentage not using the generic expertise_action_all modifier.
expertiseActionCostMod += getEnhancedSkillStatisticModifierUncapped(self, "expertise_action_all");
if(specialLine != null && specialLine != "")
expertiseActionCostMod += getEnhancedSkillStatisticModifierUncapped(self, "expertise_action_line_" + specialLine);
if(actionName != null && actionName != "")
expertiseActionCostMod += getEnhancedSkillStatisticModifierUncapped(self, "expertise_action_single_" + actionName);
actionCost = actionCost * (1 - (expertiseActionCostMod/100));
}
// Action Burn: Lose health based on action used.
if(utils.hasScriptVar(self, "buff.action_burn.value") && actionCost > 0)
{
int rightNow = getGameTime();
int timestamp = utils.getIntScriptVar(self, "buff.action_burn.timestamp");
if(rightNow > timestamp)
{
utils.setScriptVar(self, "buff.action_burn.timestamp", rightNow);
float burnRatio = utils.getFloatScriptVar(self, "buff.action_burn.value");
float actionBurn = (float)actionCost * (burnRatio/100);
int currentHealth = getHealth(self);
int modifiedHealth = currentHealth - (int)actionBurn;
if(modifiedHealth < 1)
{
modifiedHealth = 1;
}
setHealth(self, modifiedHealth);
int[] burnBuffs = buff.getAllBuffsByEffect(self, "action_burn");
obj_id burnBuffOwner = self;
if(burnBuffs.length > 0 && utils.hasScriptVar(self, "buffOwner." + burnBuffs[0]))
{
burnBuffOwner = utils.getObjIdScriptVar(self, "buffOwner." + burnBuffs[0]);
}
pp = prose.setStringId(pp, new string_id("spam", "crippling_pain"));
showFlyTextPrivateProseWithFlags(self, burnBuffOwner, pp, 1.5f, colors.ORANGERED, FLY_TEXT_FLAG_IS_CRITICAL_HIT);
pp = prose.setStringId(pp, new string_id("cbt_spam", "crippling_pain"));
pp = prose.setTU(pp, self);
pp = prose.setDI(pp, (int)actionBurn);
sendCombatSpamMessageProse(self, burnBuffOwner, pp, true, true, true, COMBAT_RESULT_DEBUFF);
}
}
// Modify by weapon costs
//healthCost *= ((float)weaponData.attackCost / 100f);
//actionCost *= (float)weaponData.attackCost;
//mindCost *= (float)weaponData.attackCost;
cost[0] = (int)(0);
cost[1] = (int)(actionCost);
cost[2] = (int)(0);
combatLog(self, null, "getActionCost", "Final Action cost = ["+cost[0]+", "+cost[1]+", "+cost[2]+"]");
return cost;
}
// various test scripts use this function by passing in fake actionData that's not derived from a command table command
int getForceCost(obj_id self, weapon_data weaponData, dictionary actionData)
{
return 0;
}
int getForceCost(obj_id self, weapon_data weaponData, combat_data actionData)
{
return 0;
}
boolean validateTarget(obj_id target, int targetCheck)
{
obj_id self = getSelf();
if(!storyteller.storytellerCombatCheck(self, target))
{
return false;
}
switch (targetCheck)
{
case VALID_TARGET_MOB:
return isMob(target) && !vehicle.isVehicle(target);
case VALID_TARGET_CREATURE:
return ai_lib.isMonster(target);
case VALID_TARGET_NPC:
return ai_lib.isNpc(target);
case VALID_TARGET_DROID:
return ai_lib.isDroid(target) || ai_lib.isAndroid(target) ;
case VALID_TARGET_PVP:
return pvpCanAttack(self, target);
case VALID_TARGET_JEDI:
if(isPlayer(target))
return isJedi(target);
else
return jedi.isLightsaber(getCurrentWeapon(target));
case VALID_TARGET_DEAD:
return isDead(target);
case VALID_TARGET_FRIEND:
return pvpCanHelp(self, target);
}
return true;
}
string getCreatureAnimationName(obj_id self, string playbackName, hit_result hitData, weapon_data weaponData)
{
string animName = "";
int niche = ai_lib.aiGetNiche(self);
//if (niche == NICHE_DROID || niche == NICHE_VEHICLE)
if (niche == NICHE_VEHICLE)
{
animName ="droid_attack";
}
else
{
if (playbackName.endsWith("ranged"))
animName = "creature_attack_ranged";
else if (playbackName.endsWith("melee"))
animName = "creature_attack";
else
animName = "creature_attack_special_"+rand(1,2);
}
int avgDamage = (weaponData.minDamage + weaponData.maxDamage) / 2;
if (hitData.damage > avgDamage)
{
animName += "_medium";
}
else
{
animName += "_light";
}
return animName;
}
boolean checkWeaponCerts(obj_id player, weapon_data weaponData)
{
return checkWeaponCerts(player, weaponData, true);
}
boolean checkWeaponCerts(obj_id player, weapon_data weaponData, boolean verbose)
{
// Only players care about certs
if (!isPlayer(player))
return true;
// Check actual weapon cert
if(!combat.hasCertification(player, weaponData.id, verbose))
return false;
if (utils.isProfession(player, utils.FORCE_SENSITIVE))
{
if (jedi.isLightsaber(weaponData.weaponType))
{
// Make sure the saber has a color crystal
if (!jedi.hasColorCrystal(weaponData.id))
return false;
// Make sure there are not too many crystals in the saber
if (!jedi.validateCrystalCount(weaponData.id))
return false;
}
// Make sure they aren't Force Running
if (buff.hasBuff(player, "forceRun_1") || buff.hasBuff(player, "forceRun_2"))
{
return false;
}
}
return true;
}
int applyArmorProtection(obj_id attacker, obj_id defender, weapon_data weaponData, hit_result hitData, float bypassArmor)
{
return applyArmorProtection(attacker, defender, weaponData, hitData, bypassArmor, 0f);
}
int applyArmorProtection(obj_id attacker, obj_id defender, weapon_data weaponData, hit_result hitData, float bypassArmor, float expertiseDamageBonus)
{
float baseDamage = hitData.damage;
float elementalDamage = weaponData.elementalValue;
float baseProtection = 0f;
float elementalProtection = 0f;
float basePsgProtection = 0f;
float elementalPsgProtection = 0f;
boolean strikethrough = hitData.strikethrough;
int armor_neglect = getEnhancedSkillStatisticModifierUncapped(attacker, "expertise_armor_neglect");
int player_armor_reduction = getEnhancedSkillStatisticModifierUncapped(defender, "expertise_innate_reduction_all_player");
float neglectMod = 1.0f - ((float)armor_neglect / 100.0f);
float playerMod = 1.0f - ((float)player_armor_reduction / 100.0f);
obj_id armorPiece = null;
obj_id psg = null;
if (isPlayer(defender))
{
boolean isRanged = isRangedWeapon(weaponData.weaponType);
// Determine armor piece hit
//armorPiece = getArmorPieceHit(defender, hitData.hitLocation);
psg = getPsgArmor(defender);
if (isIdValid(defender) || isIdValid(psg))
{
// Get armor protection values
float[] psgProtectionReference = new float[1];
baseProtection = getArmorProtection(defender, defender, psg, weaponData.damageType, psgProtectionReference);
baseProtection *= neglectMod;
baseProtection *= playerMod;
if ( isRanged )
{
basePsgProtection = psgProtectionReference[0];
}
elementalProtection = getArmorProtection(defender, defender, psg, weaponData.elementalType, psgProtectionReference, attacker);
if ( isRanged )
elementalPsgProtection = psgProtectionReference[0];
if ( isRanged )
{
if ( basePsgProtection > 0 || elementalPsgProtection > 0 )
{
// Play PSG effect between attacker and defender
int psgLevel = armor.getArmorLevel(psg);
if ( psgLevel < 0 || psgLevel >= armor.PSG_HIT_EFFECTS.length )
psgLevel = 0;
// @todo: do we need some sort of delay here?
playClientEffectObj(defender, armor.PSG_HIT_EFFECTS[psgLevel], defender, "", transform.identity);
}
}
}
}
else
{
baseProtection = getNpcArmorProtection(defender, weaponData.damageType);
baseProtection *= neglectMod;
elementalProtection = getNpcArmorProtection(defender, weaponData.elementalType);
}
// Only apply strikethrough if this is a non armor piercing attack.
if (bypassArmor == 0 && strikethrough)
{
float strikethroughRating = (float)(getStrikethroughValue(attacker, defender) / 100.0f);
bypassArmor = rand(strikethroughRating / 2.0f ,strikethroughRating );
hitData.strikethroughAmmount = bypassArmor * 100.0f;
}
if (bypassArmor > 0)
{
if (bypassArmor > 1.0) bypassArmor = 1f;
if (bypassArmor < 0.0) bypassArmor = 0f;
}
baseProtection *= 1.0 - bypassArmor;
elementalProtection *= 1.0 - bypassArmor;
// Actually account for diminishing returns so PSGs are not GodMode versus energy.
if(basePsgProtection > 0)
basePsgProtection = scalePsgProtectionByBaseProtection(basePsgProtection, baseProtection);
// Reduce damage accordingly
int baseDmgAbsorbed = (int)(baseDamage * (baseProtection + basePsgProtection));
baseDamage -= baseDmgAbsorbed;
int blockValue = 0;
if (hitData.blockResult)
{
blockValue = combat.getBlockAmmount(defender);
if (baseDamage <= blockValue)
{
hitData.block = (int)baseDamage;
baseDamage = 0.0f;
}
else
{
baseDamage -= blockValue;
hitData.block = blockValue;
}
string_id critSpam = new string_id("combat_effects", "block");
prose_package pp = new prose_package();
pp = prose.setStringId(pp, critSpam);
pp = prose.setDI(pp, hitData.block);
showFlyTextPrivateProseWithFlags(defender, defender, pp, 1.5f, colors.LIMEGREEN, FLY_TEXT_FLAG_IS_GLANCING_BLOW );
showFlyTextPrivateProseWithFlags(defender, attacker, pp, 1.5f, colors.LIMEGREEN, FLY_TEXT_FLAG_IS_GLANCING_BLOW );
}
//baseDamage *= crit_mod; // Apply a crit mod, if sliced.(NGE removed slicing)
if ( baseDamage < 0 )
{
baseDmgAbsorbed += baseDamage;
baseDamage = 0;
}
//apply expertise bonuses to ELEMENTAL damage
if(expertiseDamageBonus != 0)
{
elementalDamage = elementalDamage * (1.0f + (expertiseDamageBonus / 100.0f) );
}
int elementalDmgAbsorbed = (int)(elementalDamage * (elementalProtection));
elementalDamage -= elementalDmgAbsorbed;
//elementalDamage *= crit_mod; // Apply a crit mod, if sliced.(NGE removed slicing)
if ( elementalDamage < 0 )
{
elementalDmgAbsorbed += elementalDamage;
elementalDamage = 0;
}
if ( (basePsgProtection > 0 || elementalPsgProtection > 0) && isIdValid(psg) )
{
// Decay psg
decayArmorPiece(psg, baseDamage * basePsgProtection + elementalDamage * elementalPsgProtection);
}
/*(NGE removed decay on armor pieces)
if ( (baseProtection > 0 || elementalProtection > 0) && isIdValid(armorPiece))
{
// Decay armor
decayArmorPiece(armorPiece, baseDamage * baseProtection + elementalDamage * elementalProtection);
}
*/
hitData.elementalDamage = (int)elementalDamage;
hitData.elementalDamageType = weaponData.elementalType;
hitData.damage = (int)baseDamage;// + hitData.elementalDamage;
if ( (baseProtection > 0 || elementalProtection > 0) && isIdValid(armorPiece) )
hitData.blockingArmor = armorPiece;
else if ( (basePsgProtection > 0 || elementalPsgProtection > 0) && isIdValid(psg))
hitData.blockingArmor = psg;
return (baseDmgAbsorbed + elementalDmgAbsorbed);
}
float scalePsgProtectionByBaseProtection(float basePsgProtection, float baseProtection)
{
if(baseProtection < .2f)
baseProtection = .2f;
basePsgProtection *= 10000;
basePsgProtection = (basePsgProtection / 100) / (100+((basePsgProtection / 100)))/(baseProtection*4);
return basePsgProtection;
}
obj_id getArmorPieceHit(obj_id defender, int hitLocation)
{
string[] armorSlots = ARMOR_SLOT_LOCATIONS[hitLocation];
string slotHit = armorSlots[rand(0, armorSlots.length-1)];
obj_id armorPiece = getObjectInSlot(defender, slotHit);
//check to see if we are clothing so as not to decay it
int armorType = getGameObjectType( armorPiece );
if (isGameObjectTypeOf(armorType, GOT_armor))
return armorPiece;
return null;
}
obj_id getPsgArmor(obj_id defender)
{
obj_id psg = getObjectInSlot(defender, PSG_SLOT_LOCATION);
if ( armor.isPsg(psg) )
return psg;
return null;
}
float getNpcArmorProtection(obj_id defender, int damageType)
{
int generalProtection = 0;
dictionary protections = null;
generalProtection = armor.getArmorGeneralProtection(defender);
//protections = armor.getArmorSpecialProtections(defender);
generalProtection += getEnhancedSkillStatisticModifier(defender, "private_armor_bonus");
generalProtection -= getEnhancedSkillStatisticModifierUncapped(defender, "expertise_innate_reduction_all_mob");
if (generalProtection == 0 && protections == null)
return 0f;
// Get pet ability bonus
if (utils.hasScriptVar(defender, "ai.combat.genProtectionBonus"))
{
generalProtection += (int)utils.getFloatScriptVar(defender, "ai.combat.genProtectionBonus");
}
// Convert protection values to percentage
float armorProtection = convertProtectionToPercent(generalProtection);
return armorProtection;
}
float getArmorProtection(obj_id defender, obj_id armorPiece, obj_id psg, int damageType, float[] psgProtectionResult)
{
return getArmorProtection(defender, armorPiece, psg, damageType, psgProtectionResult, obj_id.NULL_ID);
}
float getArmorProtection(obj_id defender, obj_id armorPiece, obj_id psg, int damageType, float[] psgProtectionResult, obj_id attacker)
{
int generalProtection = 0;
//remember the "armorPiece" is actually the player not a piece of armor
// Check skill mod armor bonus
int armorBonus = getEnhancedSkillStatisticModifier( defender, "private_armor_bonus" );
generalProtection += armorBonus;
dictionary protections = null;
dictionary psgProtections = null;
if ( isIdValid(armorPiece) )
{
generalProtection = armor.getCombatArmorGeneralProtection(armorPiece);
protections = armor.getCombatArmorSpecialProtections(armorPiece);
//LOG("ARMOR_TEST", "GetArmorProtection General Protection = " +generalProtection);
}
if ( isIdValid(psg) )
psgProtections = armor.getPsgSpecialProtections(psg);
if (generalProtection == 0 && protections == null && psgProtections == null)
return 0f;
string damageString = null;
switch (damageType)
{
case DAMAGE_KINETIC:
damageString = "kinetic";
break;
case DAMAGE_ENERGY:
damageString = "energy";
break;
case DAMAGE_BLAST:
damageString = "blast";
break;
case DAMAGE_STUN:
damageString = "stun";
break;
case DAMAGE_RESTRAINT:
damageString = "lightsaber";
break;
case DAMAGE_ELEMENTAL_HEAT:
damageString = "heat";
break;
case DAMAGE_ELEMENTAL_COLD:
damageString = "cold";
break;
case DAMAGE_ELEMENTAL_ACID:
damageString = "acid";
break;
case DAMAGE_ELEMENTAL_ELECTRICAL:
damageString = "electricity";
break;
case DAMAGE_ENVIRONMENTAL_ELECTRICAL:
damageString = "electricity";
break;
default:
break;
}
//LOG("ARMOR_TEST", "GetArmorProtection Damage Type = " +damageType);
//LOG("ARMOR_TEST", "GetArmorProtection Damage String = " +damageString);
float value = generalProtection;
float psgValue = 0;
if ( damageString != null )
{
if ( protections != null )
value += protections.getFloat(damageString);
//LOG("ARMOR_TEST", "special = " +protections.getFloat(damageString));
//as you got lower in health you would take more damage
//if ( value != 0 )
// value = reduceArmorProtectionFromDecay(armorPiece, value);
if ( psgProtections != null )
{
psgValue = psgProtections.getFloat(damageString);
if ( psgValue != 0 )
{
if ( psgProtectionResult != null && psgProtectionResult.length > 0 )
psgProtectionResult[0] = psgValue / 10000f;
}
}
}
if ( isIdValid(attacker) && damageString != null && damageString.length() > 0 )
{
// Check for the attacker having elemental penetration
// Elemental penetration allows an attacker's elemental damage to ignore an amount of a defender's elemental protection
float elementalPenetration = getElementalPenetration(attacker, damageString);
if ( elementalPenetration > 0 )
{
value *= elementalPenetration;
//LOG("ARMOR_TEST", "GetArmorProtection after elementalPenetration for " +damageString+ " = " +value );
}
}
//LOG("ARMOR_TEST", "GetArmorProtection " +damageString+ " = " +value );
value = convertProtectionToPercent(value);
//LOG("ARMOR_TEST", "GetArmorProtection FINAL VALUE" +damageString+" = " +value );
return value;
}
float getTerasKasiProtection(obj_id defender)
{
if (utils.getIntScriptVar(defender, armor.SCRIPTVAR_ARMOR_COUNT) > 0)
return 0f;
float protection = 0f;
float tkaArmor = 0f;
float jediArmor = 0f;
protection = getEnhancedSkillStatisticModifier(defender, "clothing_armor");
if (hasSkill(defender, "combat_unarmed_novice"))
tkaArmor = getSkillStatisticModifier(defender, "tka_armor");
if (tkaArmor > protection) protection = tkaArmor;
if (hasSkill(defender, "force_title_jedi_novice"))
jediArmor = getSkillStatisticModifier(defender, "jedi_armor");
if (jediArmor > protection) protection = jediArmor;
protection *= 100f;
// Check skill mod armor bonus
int armorBonus = getEnhancedSkillStatisticModifier( defender, "private_armor_bonus" );
protection += armorBonus;
return protection;
}
float convertProtectionToPercent(float protection)
{
const int reductionThreshold = 2000;
if(protection <= reductionThreshold && protection >= (-1 * reductionThreshold))
{
return protection / 10000f;
}
if(protection < 0)
{
protection += reductionThreshold; // subtract out linear progression portion
protection = protection / (100f - (protection / 100f)); // reduce protection amount
protection = -0.2f + (protection / 100f); // convert to 0 to -1 and add base linear protection amount
}
else
{
protection -= reductionThreshold; // subtract out linear progression portion
protection = protection / (100f + (protection / 100f)); // reduce protection amount
protection = 0.2f + (protection / 100f); // convert to 0-1 and add base linear protection amount
}
return protection;
}
float getArmorDecayPercentage(obj_id armorPiece)
{
float ARMOR_REDUCTION_THRESHOLD = 0.5f;
float reduction = 1.0f;
float hp = getHitpoints(armorPiece);
float maxhp = getMaxHitpoints(armorPiece);
float decayLevel = hp / maxhp;
// Reduce armor effectiveness if below threshold.
if ( hasObjVar( armorPiece, "slice.resilience" ) )
{
// If we are sliced for resilience, we delay the effects of armor reduction.
int resilience = getIntObjVar( armorPiece, "slice.resilience" );
ARMOR_REDUCTION_THRESHOLD -= resilience / 100.f;
}
if (decayLevel < ARMOR_REDUCTION_THRESHOLD)
{
reduction = 1f - (ARMOR_REDUCTION_THRESHOLD - decayLevel);
}
return reduction;
}
float reduceArmorProtectionFromDecay(obj_id armorPiece, float value)
{
float ARMOR_REDUCTION_THRESHOLD = 0.5f;
float hp = getHitpoints(armorPiece);
float maxhp = getMaxHitpoints(armorPiece);
float decayLevel = hp / maxhp;
// Reduce armor effectiveness if below threshold.
if ( hasObjVar( armorPiece, "slice.resilience" ) )
{
// If we are sliced for resilience, we delay the effects of armor reduction.
int resilience = getIntObjVar( armorPiece, "slice.resilience" );
ARMOR_REDUCTION_THRESHOLD -= resilience / 100.f;
}
if (decayLevel < ARMOR_REDUCTION_THRESHOLD)
{
float reduction = 1f - (ARMOR_REDUCTION_THRESHOLD - decayLevel);
value *= reduction;
}
return value;
}
void decayArmorPiece(obj_id armorPiece, float newDamage)
{
// if this is a psg, also reduce its efficiency
if ( armor.isPsg(armorPiece) )
{
dictionary protections = armor.getArmorSpecialProtections(armorPiece);
float maxEffectiveness = ((Float)(protections.values().iterator().next())).floatValue();
if ( maxEffectiveness > 0 )
{
float effectivenessReduction = PSG_BASE_DMG_PER_HIT + (newDamage / PSG_DAMAGE_SCALE);
float efficiencyReduction = effectivenessReduction / maxEffectiveness;
float currentEfficiency = armor.getPsgEfficiency(armorPiece);
currentEfficiency -= efficiencyReduction;
armor.setPsgEfficiency(armorPiece, currentEfficiency);
}
}
if ( utils.isAntiDecay(armorPiece) )
return;
if ( static_item.isStaticItem(armorPiece) )
return;
/*(NGE removed slicing)
// If we are sliced for deflection, we might reduce the damage the armor will take by 50%.
if ( hasObjVar( armorPiece, "slice.deflect" ) )
{
int deflect = getIntObjVar( armorPiece, "slice.deflect" );
if ( rand(1,100) < deflect )
newDamage *= 0.5f;
}
*/
/*
int hp = getHitpoints(armorPiece);
if ( hp > 0 )
{
hp -= (int)(newDamage / 2f);
setHitpoints(armorPiece, hp);
}
*/
}
boolean getGrenadeData(obj_id player, weapon_data weaponData, string params, boolean verbose)
{
Long lngId;
try
{
lngId = new Long(params); // convert the string to a long so we can make an obj_id
} // try with error handling
catch (NumberFormatException err)
{
if (verbose)
sendSystemMessage(player, new string_id("cbt_spam", "grenade_bad_param"));
return false;
}
obj_id grenade = obj_id.getObjId(lngId.longValue());
if (!isIdValid(grenade))
{
if (verbose)
sendSystemMessage(player, new string_id("cbt_spam", "grenade_invalid"));
return false;
}
if (getFirstParentInWorld(grenade) != player)
{
if (verbose)
sendSystemMessage(player, new string_id("cbt_spam", "grenade_not_owned"));
removeObjVar(grenade, "intUsed");
return false;
}
// set weapon data
weaponData.id = grenade;
weaponData.weaponName = getNameStringId(grenade);
weaponData.minDamage = getWeaponMinDamage(grenade);
weaponData.maxDamage = getWeaponMaxDamage(grenade);
weaponData.weaponType = WEAPON_TYPE_THROWN;
weaponData.attackType = ATTACK_TYPE_THROWN;
weaponData.damageType = getWeaponDamageType(grenade);
weaponData.elementalType = getWeaponElementalType(grenade);
weaponData.elementalValue = getWeaponElementalValue(grenade);
weaponData.attackSpeed = getWeaponAttackSpeed(grenade);
weaponData.woundChance = getWeaponWoundChance(grenade);
range_info rng = getWeaponRangeInfo(grenade);
weaponData.minRange = rng.minRange;
weaponData.maxRange = rng.maxRange;
weaponData.accuracy = getWeaponAccuracy(grenade);
weaponData.damageRadius = getWeaponDamageRadius(grenade);
weaponData.attackCost = getWeaponAttackCost(grenade);
return true;
}
boolean canPaintTarget(obj_id squadLeader, obj_id target)
{
// Just to keep things confusing the old Volley Fire command has been renamed to Paint Target
// Pretend all "volley" references here are "paint"
if(!isValidId(target))
{
return false;
}
if (!pvpCanAttack(squadLeader, target))
{
return false;
}
// Validate group
obj_id groupId = getGroupObject(squadLeader);
if(!isIdValid(groupId))
{
return false;
}
// make sure no one in this group has execute paintTarget recently
// - prevents people switching out squad leaders to confuse the target particle managing
if(utils.hasScriptVar(groupId, VAR_GROUP_LAST_VOLLEY_TIME))
{
int lastVolley = utils.getIntScriptVar(groupId, VAR_GROUP_LAST_VOLLEY_TIME);
int now = getGameTime();
int interval = now - lastVolley;
if(interval < VOLLEY_INTERVAL)
{
return false;
}
}
// Validate group size
obj_id[] groupMembers = getGroupMemberIds(groupId);
if(groupMembers == null || groupMembers.length < 2 )
{
return false;
}
return true;
}
void doPaintTarget(obj_id squadLeader, obj_id target)
{
obj_id groupId = getGroupObject(squadLeader);
obj_id[] groupMembers = getGroupMemberIds(groupId);
if(!hasScript(target, "systems.combat.volleytarget"))
{
attachScript(target, "systems.combat.volleytarget");
}
utils.setScriptVar(groupId, VAR_GROUP_VOLLEY_TARGET, target);
utils.setScriptVar(groupId, VAR_GROUP_LAST_VOLLEY_TIME, getGameTime());
dictionary parms = new dictionary();
parms.put("objGroup", groupId);
messageTo(target, "msgMarkedByGroup", parms, 0, false);
messageTo(target, "msgUnmarkedByGroup", parms, VOLLEY_INTERVAL * 0.7f , false); // VolleyFire has a 30 second cooldown, lasts for ~21 seconds
// show group members the particle
playClientEffectObj(groupMembers, "appearance/pt_special_attack_volley_fire.prt", target, "", new transform(), ID_VOLLEY_FIRE_PARTICLE);
}
boolean canUseWeaponWithAbility(obj_id self, weapon_data weaponData, combat_data actionData, boolean verbose)
{
if(!isIdValid(weaponData.id))
return false;
if( ((actionData.invalidWeapon & weaponTypeToBitValueMap[weaponData.weaponType]) == 0)
&& ((actionData.validWeapon & weaponTypeToBitValueMap[weaponData.weaponType]) != 0) )
return true;
if(verbose)
{
sendCombatSpamMessage(self, new string_id("cbt_spam", "invalid_weapon"));
sendSystemMessage(self, new string_id("cbt_spam", "invalid_weapon_single"));
}
return false;
}
boolean canUseWeaponWithAbility(obj_id self, weapon_data weaponData, string actionName, boolean verbose)
{
return canUseWeaponWithAbility(self, weaponData, combat_engine.getCombatData(actionName), verbose);
//ai will stop using force powers if you touch this DAN DO NOT REMOVE THIS
//DAN DO NOT TOUCH THIS
//DAN DO NOT TOUCH THIS
// LINES Below have been removed by Dan
}
void addHateProcess(obj_id attacker, obj_id defender, hit_result hitData, combat_data actionData)
{
//base class exceptions if you try to assign yourself as a hate target of yourself
if(defender == attacker)
return;
float hateMod = actionData.hateDamageModifier;
int maxHate = actionData.maxHate;
int hateAdd = actionData.hateAdd;
int hateAddTime = actionData.hateAddTime;
int hateReduction = actionData.hateReduce;
if (hitData.success)
{
if(hateReduction < 1)
{
// Max Hate
//This is a taunt. It will set you to the targets max hate value + the passed maxHate
if (maxHate > 0)
{
const float maxHateDamage = getMaxHate(defender);
setHate(defender, attacker, maxHateDamage + maxHate);
}
// Hate DOT
if (hateAdd > 0 && hateAddTime > 0)
{
addHateDot(defender, attacker, hateAdd, hateAddTime);
}
// Hate Damage
float totalHateDamage = hitData.damage * hateMod;
if (totalHateDamage > 0.0f)
{
int skillBonus = getEnhancedSkillStatisticModifier( attacker, "private_taunt_bonus" );
totalHateDamage += totalHateDamage * (skillBonus / 100f);
if ( utils.hasScriptVar( attacker, "ai.combat.aggroBonus" ) )
{
totalHateDamage += totalHateDamage * utils.getFloatScriptVar( attacker, "ai.combat.aggroBonus" );
}
if ( utils.hasScriptVar( attacker, "ai.combat.aggroBonusFlat" ) )
{
totalHateDamage += utils.getFloatScriptVar( attacker, "ai.combat.aggroBonusFlat" );
}
if ( utils.hasScriptVar( attacker, "ai.combat.aggroReduction" ) )
{
totalHateDamage -= totalHateDamage * utils.getFloatScriptVar( attacker, "ai.combat.aggroReduction" );
}
if(buff.hasBuff(attacker, BUFF_HATE_XFER))
{
int hateTransferBonus = getEnhancedSkillStatisticModifier(attacker, "expertise_aggro_channel");
float hateTransfered = totalHateDamage * (hateTransferBonus / 100f);
totalHateDamage -= hateTransfered;
obj_id transferTo = utils.getObjIdScriptVar(attacker, buff.AGGRO_TRANSFER_TO);
addHate(defender, transferTo, hateTransfered);
}
addHate(defender, attacker, totalHateDamage);
}
}
else
{
//Players and Beasts may perform a deAgro special on an NPC, non-beast.
if ((isPlayer(attacker) || beast_lib.isBeast(attacker)) && (isMob(defender) && !beast_lib.isBeast(defender)))
{
float currentHate = getHate(defender, attacker);
currentHate -= hateReduction;
currentHate = currentHate < 1.0f ? 1.0f : currentHate;
setHate(defender, attacker, currentHate);
}
else if ((isMob(attacker) && !beast_lib.isBeast(attacker)) && (isPlayer(defender) || beast_lib.isBeast(defender)))
{ // If it is an NPC using this attack on a player, the deAgro applies to the attacking NPC
float currentHate = getHate(attacker, defender);
currentHate -= hateReduction;
currentHate = currentHate < 1.0f ? 1.0f : currentHate;
setHate(attacker, defender, currentHate);
}
}
}
else
{
// Miss attack
addHate(defender, attacker, 1.0f);
}
// This is only for attacking players because otherwise it would reset an
// attacking AI's hate list timer. If the attacking AI's hate list timer
// gets reset everytime they attack a player, they would never break combat
// with a retreating player. We want to allow players to get away from AI.
if (isPlayer(attacker))
{
// Reset the attacking player's hate list timer and make sure the defender is
// on the attacking player's hate list.
addHate(attacker, defender, 0.0f);
// An attacking player continues to reset the hate timers on every AI
// of whom the attacking player is a primary hate target.
{
const obj_id[] hateList = getHateList(attacker);
for (int i = 0; i < hateList.length; ++i)
{
const obj_id hateTarget = hateList[i];
if (!isPlayer(hateTarget))
{
if (getHateTarget(hateTarget) == attacker)
{
resetHateTimer(hateTarget);
}
}
}
}
}
}
// Combat Value Caching
boolean cachedIsRidingVehicle(obj_id attacker)
{
string cacheId = "combat.cache.ridingVehicle";
if (hasCachedCombatValue(attacker, cacheId))
return getCachedCombatValueBool(attacker, cacheId);
boolean ridingVehicle = vehicle.isRidingVehicle(attacker);
setCachedCombatValue(attacker, cacheId, ridingVehicle);
return ridingVehicle;
}
boolean cachedIsGalloping(obj_id attacker)
{
string cacheId = "combat.cache.galloping";
if (hasCachedCombatValue(attacker, cacheId))
return getCachedCombatValueBool(attacker, cacheId);
obj_id mount = getMountId(attacker);
boolean galloping = false;
if (isIdValid(mount))
{
galloping = pet_lib.isGalloping(mount);
}
setCachedCombatValue(attacker, cacheId, galloping);
return galloping;
}
boolean cachedIsDead(obj_id attacker)
{
string cacheId = "combat.cache.isDead";
if (hasCachedCombatValue(attacker, cacheId))
return getCachedCombatValueBool(attacker, cacheId);
boolean dead = isDead(attacker);
setCachedCombatValue(attacker, cacheId, dead);
return dead;
}
boolean cachedIsIncapacitated(obj_id attacker)
{
string cacheId = "combat.cache.isIncapped";
if (hasCachedCombatValue(attacker, cacheId))
return getCachedCombatValueBool(attacker, cacheId);
boolean incapped = isIncapacitated(attacker);
setCachedCombatValue(attacker, cacheId, incapped);
return incapped;
}
int cachedGetLocomotion(obj_id attacker)
{
string cacheId = "combat.cache.locomotion";
if (hasCachedCombatValue(attacker, cacheId))
return getCachedCombatValueInt(attacker, cacheId);
int locomotion = getLocomotion(attacker);
setCachedCombatValue(attacker, cacheId, locomotion);
return locomotion;
}
float cachedGetMovementSpeed(obj_id attacker)
{
string cacheId = "combat.cache.moveSpeed";
if (hasCachedCombatValue(attacker, cacheId))
return getCachedCombatValueFloat(attacker, cacheId);
float moveSpeed = getMovementSpeed(attacker);
setCachedCombatValue(attacker, cacheId, moveSpeed);
return moveSpeed;
}
float cachedGetRunSpeed(obj_id attacker)
{
string cacheId = "combat.cache.runSpeed";
if (hasCachedCombatValue(attacker, cacheId))
return getCachedCombatValueFloat(attacker, cacheId);
float runSpeed = getRunSpeed(attacker);
setCachedCombatValue(attacker, cacheId, runSpeed);
return runSpeed;
}
float cachedGetDistance(obj_id attacker, obj_id defender)
{
string cacheId = "combat.cache."+defender+".distance";
if (hasCachedCombatValue(attacker, cacheId))
return getCachedCombatValueFloat(attacker, cacheId);
float dist = getDistance(attacker, defender);
setCachedCombatValue(attacker, cacheId, dist);
return dist;
}
float cachedGetSpeedAdjustMin(obj_id attacker, obj_id defender)
{
string cacheId = "combat.cache."+defender+".speedAdjustMin";
if (hasCachedCombatValue(attacker, cacheId))
return getCachedCombatValueFloat(attacker, cacheId);
float adjustMin = 0.0f;
if (cachedGetLocomotion(attacker) == LOCOMOTION_RUNNING)
{
//sendSystemMessageTestingOnly(attackerData.id, "Attacker Speed: " + getMovementSpeed(attackerData.id) + " Defender Speed: " + getMovementSpeed(defenderData[0].id));
// put on even ground, so we only take into account 2d angles during isFacing
location defenderLocation = getLocation(defender);
defenderLocation.y = getLocation(attacker).y;
if (!isFacing(attacker, defenderLocation))
adjustMin -= cachedGetMovementSpeed(attacker) * 0.6f;
}
if (cachedGetLocomotion(defender) == LOCOMOTION_RUNNING)
{
// put on even ground, so we only take into account 2d angles during isFacing
location attackerLocation = getLocation(attacker);
attackerLocation.y = getLocation(defender).y;
if (isFacing(defender, attackerLocation))
adjustMin -= cachedGetMovementSpeed(defender) * 0.6f;
}
// flatten out the adjust min and max to their max values
float maxAdjustValue = (cachedGetRunSpeed(attacker) + cachedGetRunSpeed(defender)) * 0.75f;
if (adjustMin < -maxAdjustValue)
adjustMin = -maxAdjustValue;
setCachedCombatValue(attacker, cacheId, adjustMin);
return adjustMin;
}
float cachedGetSpeedAdjustMax(obj_id attacker, obj_id defender)
{
string cacheId = "combat.cache."+defender+".speedAdjustMax";
if (hasCachedCombatValue(attacker, cacheId))
return getCachedCombatValueFloat(attacker, cacheId);
float adjustMax = 0.0f;
if (cachedGetLocomotion(attacker) == LOCOMOTION_RUNNING)
{
//sendSystemMessageTestingOnly(attackerData.id, "Attacker Speed: " + getMovementSpeed(attackerData.id) + " Defender Speed: " + getMovementSpeed(defenderData[0].id));
// put on even ground, so we only take into account 2d angles during isFacing
location defenderLocation = getLocation(defender);
defenderLocation.y = getLocation(attacker).y;
if (isFacing(attacker, defenderLocation))
adjustMax += cachedGetMovementSpeed(attacker) * 0.6f;
}
if (cachedGetLocomotion(defender) == LOCOMOTION_RUNNING)
{
// put on even ground, so we only take into account 2d angles during isFacing
location attackerLocation = getLocation(attacker);
attackerLocation.y = getLocation(defender).y;
if (!isFacing(defender, attackerLocation))
adjustMax += cachedGetMovementSpeed(defender) * 0.6f;
}
// flatten out the adjust min and max to their max values
float maxAdjustValue = (cachedGetRunSpeed(attacker) + cachedGetRunSpeed(defender)) * 0.75f;
if (adjustMax > maxAdjustValue)
adjustMax = maxAdjustValue;
setCachedCombatValue(attacker, cacheId, adjustMax);
return adjustMax;
}
boolean cachedCanSee(obj_id attacker, obj_id defender)
{
// string cacheId = "combat.cache."+defender+".canSee";
// if (hasCachedCombatValue(attacker, cacheId))
// return getCachedCombatValueBool(attacker, cacheId);
// boolean attackerCanSee = canSee(attacker, defender);
// setCachedCombatValue(attacker, cacheId, attackerCanSee);
//return attackerCanSee;
return canSee(attacker, defender);
}
boolean cachedPvpCanAttack(obj_id attacker, obj_id defender)
{
string cacheId = "combat.cache."+defender+".pvpCanAttack";
if (hasCachedCombatValue(attacker, cacheId))
return getCachedCombatValueBool(attacker, cacheId);
boolean attackerCanAttack = pvpCanAttack(attacker, defender);
setCachedCombatValue(attacker, cacheId, attackerCanAttack);
return attackerCanAttack;
}
boolean cachedIsSameSocialGroup(obj_id attacker, obj_id defender)
{
string cacheId = "combat.cache."+defender+".sameSocialGroup";
if (hasCachedCombatValue(attacker, cacheId))
return getCachedCombatValueBool(attacker, cacheId);
string attackerSocialGroup = null;
if (!isPlayer(attacker) && !pet_lib.isPet(attacker) && !pet_lib.isPet(defender))
{
attackerSocialGroup = ai_lib.getSocialGroup(attacker);
}
boolean sameSocialGroup = (attackerSocialGroup != null && attackerSocialGroup.equals(ai_lib.getSocialGroup(defender)));
setCachedCombatValue(attacker, cacheId, sameSocialGroup);
return sameSocialGroup;
}
boolean isCachedServerFrame(obj_id attacker)
{
int serverFrame = utils.getIntScriptVar(attacker, "combat.cache.serverFrame");
int currentFrame = getServerFrame();
return (serverFrame == currentFrame);
}
void setCachedServerFrame(obj_id attacker)
{
if (!isCachedServerFrame(attacker))
{
utils.removeScriptVarTree(attacker, "combat.cache");
int currentFrame = getServerFrame();
utils.setScriptVar(attacker, "combat.cache.serverFrame", currentFrame);
}
}
boolean hasCachedCombatValue(obj_id attacker, string cacheId)
{
if (isCachedServerFrame(attacker))
{
return (utils.hasScriptVar(attacker, "combat.cache."+cacheId));
}
return false;
}
int getCachedCombatValueInt(obj_id attacker, string cacheId)
{
if (isCachedServerFrame(attacker))
{
return (utils.getIntScriptVar(attacker, "combat.cache."+cacheId));
}
return 0;
}
float getCachedCombatValueFloat(obj_id attacker, string cacheId)
{
if (isCachedServerFrame(attacker))
{
return (utils.getFloatScriptVar(attacker, "combat.cache."+cacheId));
}
return 0f;
}
boolean getCachedCombatValueBool(obj_id attacker, string cacheId)
{
if (isCachedServerFrame(attacker))
{
return (utils.getBooleanScriptVar(attacker, "combat.cache."+cacheId));
}
return false;
}
void setCachedCombatValue(obj_id attacker, string cacheId, int value)
{
setCachedServerFrame(attacker);
utils.setScriptVar(attacker, "combat.cache."+cacheId, value);
}
void setCachedCombatValue(obj_id attacker, string cacheId, float value)
{
setCachedServerFrame(attacker);
utils.setScriptVar(attacker, "combat.cache."+cacheId, value);
}
void setCachedCombatValue(obj_id attacker, string cacheId, boolean value)
{
setCachedServerFrame(attacker);
utils.setScriptVar(attacker, "combat.cache."+cacheId, value);
}
void combatLog(obj_id attacker, obj_id defender, string logName, string logMsg)
{
// DO NOT UNCOMMENT THIS AND SUBMIT - UNCOMMENT THE DEFINE AT THE TOP OF THIS FILE
// AND DON'T SUBMIT WITH COMBAT LOGGING ON - IT'S FOR LOCAL TESTING ONLY!
#ifdef COMBATLOG
const string[] enabledLogs = {
"combatStandardAction",
"getCorrectCombatTarget",
"doCombatPreCheck",
"getCombatDefenders",
"validateDefenders",
"getActionCost",
"canDrainCombatActionAttributes",
"drainCombatActionAttributes",
"runHitEngine",
"getActionAnimation",
"getRawDamage",
"applyDamage",
"doWrappedDamage",
"applyGrenadeEffects",
"applyAttackerCombatBuffs",
"applyDefenderCombatBuffs",
"healingStandardAction",
"getCorrectHealingTarget",
"validateHealingTargets",
"doHealingPreCheck",
"setDefenderCombatResults",
"checkCombatStates",
"checkPosture",
"end" };
if (utils.getElementPositionInArray (enabledLogs, logName) == -1)
return;
string attackerName = "NULL";
string defenderName = "NULL";
if (isIdValid(attacker))
{
attackerName = getEncodedName(attacker);
if (attackerName.startsWith("@")) attackerName = localize(getNameStringId(attacker));
}
if (isIdValid(defender))
{
defenderName = getEncodedName(defender);
if (defenderName.startsWith("@")) defenderName = localize(getNameStringId(defender));
}
LOG ("combat_log", attackerName+"("+attacker+") ATTACKS "+defenderName+"("+defender+")\t:"+logName+"::"+logMsg);
// DO NOT UNCOMMENT THIS AND SUBMIT - UNCOMMENT THE DEFINE AT THE TOP OF THIS FILE
// AND DON'T SUBMIT WITH COMBAT LOGGING ON - IT'S FOR LOCAL TESTING ONLY!
#endif
}
int getWeaponCategory(string weaponType)
{
if (weaponType.equals("rifle")) return RANGED_WEAPON;
if (weaponType.equals("carbine")) return RANGED_WEAPON;
if (weaponType.equals("pistol")) return RANGED_WEAPON;
if (weaponType.equals("heavyweapon")) return RANGED_WEAPON;
if (weaponType.equals("onehandmelee")) return MELEE_WEAPON;
if (weaponType.equals("twohandmelee")) return MELEE_WEAPON;
if (weaponType.equals("unarmed")) return MELEE_WEAPON;
if (weaponType.equals("polearm")) return MELEE_WEAPON;
if (weaponType.equals("thrown")) return RANGED_WEAPON;
if (weaponType.equals("onehandlightsaber")) return MELEE_WEAPON;
if (weaponType.equals("twohandlightsaber")) return MELEE_WEAPON;
if (weaponType.equals("polearmlightsaber")) return MELEE_WEAPON;
if (weaponType.equals("force")) return FORCE_POWER;
return 0;
}
int getWeaponCategory(int weaponType)
{
if (weaponType == WEAPON_TYPE_RIFLE) return RANGED_WEAPON;
if (weaponType == WEAPON_TYPE_LIGHT_RIFLE) return RANGED_WEAPON;
if (weaponType == WEAPON_TYPE_PISTOL) return RANGED_WEAPON;
if (weaponType == WEAPON_TYPE_HEAVY) return RANGED_WEAPON;
if (weaponType == WEAPON_TYPE_1HAND_MELEE) return MELEE_WEAPON;
if (weaponType == WEAPON_TYPE_2HAND_MELEE) return MELEE_WEAPON;
if (weaponType == WEAPON_TYPE_UNARMED) return MELEE_WEAPON;
if (weaponType == WEAPON_TYPE_POLEARM) return MELEE_WEAPON;
if (weaponType == WEAPON_TYPE_THROWN) return RANGED_WEAPON;
if (weaponType == WEAPON_TYPE_WT_1HAND_LIGHTSABER) return MELEE_WEAPON;
if (weaponType == WEAPON_TYPE_WT_2HAND_LIGHTSABER) return MELEE_WEAPON;
if (weaponType == WEAPON_TYPE_WT_POLEARM_LIGHTSABER) return MELEE_WEAPON;
if (weaponType == WEAPON_TYPE_FORCE_POWER) return FORCE_POWER;
return 0;
}
string getWeaponStringCategory(int weaponType)
{
int weaponCategory = getWeaponCategory(weaponType);
if (weaponCategory == RANGED_WEAPON) return "ranged";
if (weaponCategory == MELEE_WEAPON) return "melee";
if (weaponCategory == FORCE_POWER) return "ranged";
return null;
}
string getWeaponStringCategory(string weaponType)
{
int weaponCategory = getWeaponCategory(weaponType);
if (weaponCategory == RANGED_WEAPON) return "ranged";
if (weaponCategory == MELEE_WEAPON) return "melee";
if (weaponCategory == FORCE_POWER) return "ranged";
return null;
}
int getWeaponType(string weaponType)
{
if (weaponType.equals("rifle")) return WEAPON_TYPE_RIFLE;
if (weaponType.equals("carbine")) return WEAPON_TYPE_LIGHT_RIFLE;
if (weaponType.equals("pistol")) return WEAPON_TYPE_PISTOL;
if (weaponType.equals("heavyweapon")) return WEAPON_TYPE_HEAVY;
if (weaponType.equals("onehandmelee")) return WEAPON_TYPE_1HAND_MELEE;
if (weaponType.equals("twohandmelee")) return WEAPON_TYPE_2HAND_MELEE;
if (weaponType.equals("unarmed")) return WEAPON_TYPE_UNARMED;
if (weaponType.equals("polearm")) return WEAPON_TYPE_POLEARM;
if (weaponType.equals("thrown")) return WEAPON_TYPE_THROWN;
if (weaponType.equals("onehandlightsaber")) return WEAPON_TYPE_WT_1HAND_LIGHTSABER;
if (weaponType.equals("twohandlightsaber")) return WEAPON_TYPE_WT_2HAND_LIGHTSABER;
if (weaponType.equals("polearmlightsaber")) return WEAPON_TYPE_WT_POLEARM_LIGHTSABER;
return 0;
}
int getCorrectedWeaponType(int passedType)
{
switch(passedType)
{
case WEAPON_TYPE_GROUND_TARGETTING:
return WEAPON_TYPE_HEAVY;
case WEAPON_TYPE_DIRECTIONAL:
return WEAPON_TYPE_HEAVY;
}
return passedType;
}
string getWeaponStringType(int weaponType)
{
switch(weaponType)
{
case WEAPON_TYPE_RIFLE : return "rifle";
case WEAPON_TYPE_LIGHT_RIFLE : return "carbine";
case WEAPON_TYPE_PISTOL : return "pistol";
case WEAPON_TYPE_HEAVY : return "heavyweapon";
case WEAPON_TYPE_1HAND_MELEE : return "onehandmelee";
case WEAPON_TYPE_2HAND_MELEE: return "twohandmelee";
case WEAPON_TYPE_UNARMED: return "unarmed";
case WEAPON_TYPE_POLEARM: return "polearm";
case WEAPON_TYPE_THROWN: return "thrown";
case WEAPON_TYPE_WT_1HAND_LIGHTSABER: return "onehandlightsaber";
case WEAPON_TYPE_WT_2HAND_LIGHTSABER: return "twohandlightsaber";
case WEAPON_TYPE_WT_POLEARM_LIGHTSABER: return "polearmlightsaber";
case WEAPON_TYPE_FORCE_POWER: return "force";
}
return "";
}
boolean isMeleeWeapon(obj_id weapon)
{
int weaponType = getWeaponType(weapon);
return isMeleeWeapon(weaponType);
}
boolean isMeleeWeapon(int weaponType)
{
return hasBit(weaponType, B_MELEE);
}
boolean isRangedWeapon(obj_id weapon)
{
int weaponType = getWeaponType(weapon);
return isRangedWeapon(weaponType);
}
boolean isHeavyWeapon(int weaponType)
{
return hasBit(weaponType, B_HEAVY);
}
boolean isHeavyWeapon(obj_id weapon)
{
int weaponType = getWeaponType(weapon);
return hasBit(weaponType, B_HEAVY);
}
boolean isRangedWeapon(int weaponType)
{
return hasBit(weaponType, B_RANGED);
}
boolean isLightsaberWeapon(obj_id weapon)
{
int weaponType = getWeaponType(weapon);
return isLightsaberWeapon(weaponType);
}
boolean isLightsaberWeapon(int weaponType)
{
return hasBit(weaponType, B_ALL_LIGHTSABERS);
}
boolean isGrenade(weapon_data weaponData)
{
return hasBit(weaponData.weaponType, B_THROWN);
}
boolean isHeavyWeapon(weapon_data weaponData)
{
return hasBit(weaponData.weaponType, B_HEAVY);
}
boolean canApplyCombatEffect(obj_id objPlayer)
{
if ( !isIdValid(objPlayer) )
return false;
if ( ai_lib.aiGetNiche( objPlayer)==NICHE_DROID || vehicle.isVehicle(objPlayer) ||
ai_lib.isTurret(objPlayer) || ai_lib.isAndroid(objPlayer))
{
return false;
}
return true;
}
boolean hasCertification(obj_id objPlayer, obj_id objWeapon)
{
return hasCertification(objPlayer, objWeapon, true);
}
boolean hasCertification(obj_id objPlayer, obj_id objWeapon, boolean verbose)
{
boolean hasCert = true;
// Since datatable queries are so expensive, check the last weapon checked against the current weapon
if(utils.hasScriptVar(objPlayer, "combat.weaponCertified"))
{
obj_id lastCertified = utils.getObjIdScriptVar(objPlayer, "combat.weaponCertified");
if(lastCertified == objWeapon)
{
return true;
}
}
string template = getTemplateName(objWeapon);
if(template == null)
return false;
// unarmed player weapon is object/weapon/melee/unarmed/unarmed_default_player.iff
if(template == "object/weapon/melee/unarmed/unarmed_default_player.iff")
{
return true;
}
// check for racial certs
int speciesRequired = dataTableGetInt(combat.WEAPON_LEVEL_TABLE, template, "species_restriction");
if(speciesRequired >= 0)
{
int playerSpecies = getSpecies(objPlayer);
if(playerSpecies != speciesRequired)
{
hasCert = false;
}
}
if(hasObjVar (objWeapon, "factionrestricted.rebel"))
{
if(!factions.isRebel(objPlayer))
{
hasCert = false;
}
}
if(hasObjVar(objWeapon, "factionrestricted.imperial"))
{
if(!factions.isImperial(objPlayer))
{
hasCert = false;
}
}
string classTemplate = getSkillTemplate(objPlayer);
if (isLightsaberWeapon(objWeapon) && !utils.isProfession(objPlayer, utils.FORCE_SENSITIVE))
hasCert = false;
//static weapon check
if(static_item.isDynamicItem(objWeapon))
{
int levelRequired = getIntObjVar(objWeapon, "dynamic_item.intLevelRequired");
int playerLevel = getLevel(objPlayer);
if(playerLevel < levelRequired)
{
hasCert = false;
}
}
else if(static_item.isStaticItem(objWeapon))
{
// Check skill requirement
dictionary itemData = static_item.getMasterItemDictionary(objWeapon);
string skillRequired = itemData.getString("required_skill");
if (skillRequired != null && skillRequired !="")
{
if (classTemplate != null && classTemplate !="")
{
if(!classTemplate.startsWith(skillRequired))
{
hasCert = false;
}
}
}
// Check level requirement
int levelRequired = itemData.getInt("required_level");
int playerLevel = getLevel(objPlayer);
if(playerLevel < levelRequired)
{
hasCert = false;
}
}
//crafted weapon check
else
{
// Check skill requirement
string skillRequired = dataTableGetString(WEAPON_LEVEL_TABLE, template, "secondary_restriction");
if (skillRequired != null && skillRequired !="")
{
if (classTemplate != null && classTemplate !="")
{
if(!classTemplate.startsWith(skillRequired))
{
hasCert = false;
}
}
}
// Check level requirement
//Post GU5 we changed the way weapons are crafted. So we need to see if this is one of our "new" weapons
int levelRequired = -1;
int playerLevel = getLevel(objPlayer);
if(hasObjVar(objWeapon, weapons.OBJVAR_WP_LEVEL))
{
levelRequired = getIntObjVar(objWeapon, weapons.OBJVAR_WP_LEVEL);
}
else
{
levelRequired = dataTableGetInt(WEAPON_LEVEL_TABLE, template, "weapon_level");
}
if(playerLevel < levelRequired)
{
hasCert = false;
}
}
if (!hasCert && isGod(objPlayer))
{
//sendSystemMessageTestingOnly(objPlayer, "GOD MODE: Passing Certification check due to GOD MODE");
hasCert = true;
}
if(hasCert)
{
utils.setScriptVar(objPlayer, "combat.weaponCertified", objWeapon);
}
return hasCert;
}
void applyCombatSpeedDelay(obj_id target, float delay, int duration)
{
int delayStampStart = getGameTime();
int delayStampEnd = delayStampStart + duration;
utils.setScriptVar(target, "speedDelayEffectStart", delayStampStart);
utils.setScriptVar(target, "speedDelayEffectEnd", delayStampEnd);
utils.setScriptVar(target, "speedDelayPotency", delay);
return;
}//end applyCombatSpeedDelay
////////////////////////////////////////////////////////////////////////////////////////////
// Combat Action datatable accessors
////////////////////////////////////////////////////////////////////////////////////////////
void doCombatDebuffs (obj_id self)
{
if(!utils.hasScriptVar(self, "fltNonCombatHealthRegen"))
{
float fltHealthRegen = getHealthRegenRate(self);
utils.setScriptVar(self, "fltNonCombatHealthRegen", fltHealthRegen);
setRegenRate(self, HEALTH, 0);
}
}
boolean clearCombatDebuffs (obj_id self)
{
if (getGameTime() > utils.getIntScriptVar(self, "incap.timeStamp"))
{
if(utils.hasScriptVar(self, "fltNonCombatHealthRegen"))
{
float fltHealthRegen = utils.getFloatScriptVar(self, "fltNonCombatHealthRegen");
setRegenRate(self, HEALTH, fltHealthRegen);
utils.removeScriptVar(self, "fltNonCombatHealthRegen");
return true;
}
}
return false;
}
/*
void applyWeaponHinderance(obj_id objPlayer)
{
obj_id weapon = getHeldWeapon(objPlayer);
applyWeaponHinderance(objPlayer, weapon);
}
void applyWeaponHinderance(obj_id owner, obj_id weapon)
{
if(!isIdValid(owner))
return;
float hinderance = getWeaponHinderance(weapon);
if(hinderance == 0.0f)
{
if(buff.hasBuff(owner, "weaponHinderance"))
buff.removeBuff(owner, "weaponHinderance");
}
else
buff.applyBuff(owner, "weaponHinderance", 0f, hinderance);
}
void clearWeaponHinderance(obj_id player)
{
obj_id weapon = getCurrentWeapon(player);
clearWeaponHinderance(player, weapon);
}
void clearWeaponHinderance(obj_id owner, obj_id weapon)
{
float hinderance = getWeaponHinderance(weapon);
if(hinderance == 0.0f)
{
if(buff.hasBuff(owner, "weaponHinderance"))
buff.removeBuff(owner, "weaponHinderance");
}
else
buff.applyBuff(owner, "weaponHinderance", 15f, hinderance);
}
float getWeaponHinderance(obj_id weapon)
{
float hinderance = 0f;
if(isIdValid(weapon))
{
int type = getWeaponType(weapon);
switch(type)
{
case WEAPON_TYPE_HEAVY: hinderance = 20.0f;
break;
case WEAPON_TYPE_RIFLE: hinderance = 18.0f;
break;
case WEAPON_TYPE_LIGHT_RIFLE: hinderance = 13.0f;
break;
case WEAPON_TYPE_PISTOL: hinderance = 10.0f;
break;
}
}
return hinderance;
}
*/
boolean isInCombat(obj_id objTarget)
{
return getState(objTarget, STATE_COMBAT) > 0 ? true : false;
}
string getAttackName(obj_id self)
{
string actionName = null;
obj_id weapon = getCurrentWeapon(self);
int weaponType = getWeaponType(weapon);
if(utils.hasScriptVar(self, DO_ONCE_ATTACK_OVERRIDE))
{
actionName = utils.getStringScriptVar(self, DO_ONCE_ATTACK_OVERRIDE);
utils.removeScriptVar(self, DO_ONCE_ATTACK_OVERRIDE);
return actionName;
}
if(utils.hasScriptVar(self, DEFAULT_ATTACK_OVERRIDE))
{
return utils.getStringScriptVar(self, DEFAULT_ATTACK_OVERRIDE);
}
if(hasObjVar(self, DEFAULT_ATTACK_OVERRIDE))
{
LOG("combat.scriptlib", "gcw getStringObjVar(self, DEFAULT_ATTACK_OVERRIDE): " + getStringObjVar(self, DEFAULT_ATTACK_OVERRIDE));
return getStringObjVar(self, DEFAULT_ATTACK_OVERRIDE);
}
if(getTemplateName(weapon).equals("object/weapon/ranged/droid/droid_flamethrower.iff"))
{
actionName = "ig88_flame_thrower";
}
else if(getTemplateName(weapon).equals("object/weapon/ranged/droid/droid_flamethrower_nomuzzle.iff"))
{
actionName = "ig88_flame_thrower_vertical";
}
else if(getTemplateName(weapon).equals("object/weapon/ranged/droid/droid_rocket_launcher.iff"))
{
actionName = "ig88_rocket_launcher";
}
else if (combat.isRangedWeapon(weaponType))
{
if (ai_lib.isHumanoid(self))
actionName = "rangedShot";
else
actionName = "creatureRangedAttack";
}
else if (combat.isMeleeWeapon(weaponType))
{
if (ai_lib.isHumanoid(self))
actionName = "meleeHit";
else
actionName = "creatureMeleeAttack";
}
else if (combat.isLightsaberWeapon(weaponType))
{
if (ai_lib.isHumanoid(self))
actionName = "saberHit";
else
actionName = "creatureMeleeAttack";
}
return actionName;
}
string getBestAction(obj_id player, string actionName)
{
// Loop through possible better actions return highest one.
for (int i = 2; i > 0; i--)
{
string testCmd = actionName+"_"+i;
if (hasCommand(player, testCmd))
return testCmd;
}
return actionName;
}
string getActionAnimation(combat_data actionData, string weaponType)
{
//sendSystemMessageTestingOnly(obj_id.getObjId(10219418), "getActionAnimation for " + weaponType);
string animation = "";
if(weaponType.equals("unarmed"))
{
animation = actionData.anim_unarmed;
}
else if(weaponType.equals("onehandmelee"))
{
//sendSystemMessageTestingOnly(obj_id.getObjId(10219418), "Matched OHM - '" + actionData.anim_onehandmelee);
animation = actionData.anim_onehandmelee;
}
else if(weaponType.equals("twohandmelee"))
{
animation = actionData.anim_twohandmelee;
}
else if(weaponType.equals("polearm"))
{
animation = actionData.anim_polearm;
}
else if(weaponType.equals("lightRifle"))
{
animation = actionData.anim_lightRifle;
}
else if(weaponType.equals("pistol"))
{
animation = actionData.anim_pistol;
}
else if(weaponType.equals("carbine"))
{
animation = actionData.anim_carbine;
}
else if(weaponType.equals("rifle"))
{
animation = actionData.anim_rifle;
}
else if(weaponType.equals("heavyweapon"))
{
animation = actionData.anim_heavyweapon;
}
else if(weaponType.equals("thrown"))
{
animation = actionData.anim_thrown;
}
else if(weaponType.equals("thrown"))
{
animation = actionData.anim_thrown;
}
else if(weaponType.equals("onehandlightsaber"))
{
animation = actionData.anim_onehandlightsaber;
}
else if(weaponType.equals("twohandlightsaber"))
{
animation = actionData.anim_twohandlightsaber;
}
else if(weaponType.equals("polearmlightsaber"))
{
animation = actionData.anim_polearmlightsaber;
}
if(animation.length() < 1)
{
animation = actionData.animDefault;
}
if (animation.startsWith("*creature_attack"))
{
//sendSystemMessageTestingOnly(obj_id.getObjId(10219418), "Creature Animation");
return getCreatureAnimationName(animation);
}
// Split out comma delimited animations and pick one randomly
string[] anims = split(animation, ',');
if (anims.length > 1)
{
animation = anims[rand(0, anims.length - 1)];
}
//sendSystemMessageTestingOnly(obj_id.getObjId(10219418), "Final Animation = " + animation);
return animation;
}
string getCreatureAnimationName(string playbackName)
{
obj_id self = getSelf();
string animName = "";
int niche = ai_lib.aiGetNiche(self);
//if (niche == NICHE_DROID || niche == NICHE_VEHICLE)
if (niche == NICHE_VEHICLE)
{
animName ="droid_attack";
}
else
{
if(playbackName.endsWith("ranged"))
{
animName = "creature_attack_ranged";
}
else if(playbackName.endsWith("melee"))
{
// Default Melee attacks have a chance to trigger one of the following attacks:
// creature_attack_heavy
// creature_attack_light
// creature_attack_special_1_heavy
// creature_attack_special_1_light
if(rand(1,2) == 1)
animName = "creature_attack";
else
animName = "creature_attack_special_1";
if(rand(1,2) == 1)
animName += "_light";
else
animName += "_heavy";
return animName;
}
else
animName = "creature_attack_special_"+rand(1,2);
}
animName += "_medium";
return animName;
}
obj_id makeTrackerEgg(obj_id objPlayer, location locTest, combat_data actionData)
{
obj_id objEgg = createObject(actionData.delayAttackEggTemplate, locTest);
if(!isIdValid(objEgg))
{
return null;
}
dictionary combatDict = combat_engine.getCombatDataDictionary(actionData);
utils.setScriptVar(objEgg, "combatDataDict", combatDict);
utils.setScriptVar(objEgg, "objOwner", objPlayer);
utils.setScriptVar(objPlayer, "objEgg", objEgg);
attachScript(objEgg, "systems.combat.combat_delayed_tracker");
return objEgg;
}
// Bitwise functions for use with Weapon Type Restrictions changes by AHunter (with minor stuff by EJMoreland)
int makeBit(int bit)
{
return (1<<(bit-1));
}
boolean hasBit(int weaponType, int bitValue)
{
if(weaponType == -1)
return false;
return ((weaponTypeToBitValueMap[weaponType] & bitValue) != 0);
}
float doCriticalHitEffect(attacker_data attackerData, defender_data defenderData, weapon_data weaponData, hit_result hitData, combat_data actionData)
{
int elementalType = weaponData.elementalType;
float elementalDamage = weaponData.elementalValue;
float damageMod = 1.5f;
hitData.critDamage =2 ; // 2: standard crit
int critResist = 0;
float heatResist = 0;
float coldResist = 0;
float acidResist = 0;
float electricResist = 0;
int resistRoll = rand(1, 10000);
// Only do any special elemental weapon stuff if we get some of our damage from a weapon.
if(actionData.percentAddFromWeapon > 0.0f)
{
if(utils.hasScriptVar(defenderData.id, armor.SCRIPTVAR_CACHED_GENERAL_PROTECTION))
{
critResist += utils.getIntScriptVar(defenderData.id, armor.SCRIPTVAR_CACHED_GENERAL_PROTECTION);
}
if(utils.hasScriptVar(defenderData.id, armor.SCRIPTVAR_CACHED_SPECIAL_PROTECTIONS) )
{
dictionary protections = utils.getDictionaryScriptVar(defenderData.id, armor.SCRIPTVAR_CACHED_SPECIAL_PROTECTIONS);
heatResist = protections.getFloat("heat");
coldResist = protections.getFloat("cold");
acidResist = protections.getFloat("acid");
electricResist = protections.getFloat("electricity");
}
switch(elementalType)
{
case DAMAGE_ELEMENTAL_HEAT:
if(resistRoll > critResist + heatResist)
{
int level = getLevel(attackerData.id);
if(level == 90)
{
buff.applyBuff(defenderData.id, attackerData.id, "crit_fire_dot_4");
}
else if(level >= 75)
{
buff.applyBuff(defenderData.id, attackerData.id, "crit_fire_dot_3");
}
else if(level >= 50)
{
buff.applyBuff(defenderData.id, attackerData.id, "crit_fire_dot_2");
}
else if(level >= 25)
{
buff.applyBuff(defenderData.id, attackerData.id, "crit_fire_dot_1");
}
damageMod = 1.5f;
hitData.critDamage = 3; // 3: heat crit
return damageMod;
}
break;
case DAMAGE_ELEMENTAL_COLD:
if(resistRoll > critResist + coldResist && !buff.hasBuff(defenderData.id, "criticalImmunity"))
{
if(!buff.hasBuff(defenderData.id, "criticalColdEffect"))
{
buff.applyBuff(defenderData.id, "criticalColdEffect", 6, (float)elementalDamage / 10.0f);
}
damageMod = 1.5f;
hitData.critDamage = 4; // 4: cold crit
return damageMod;
}
break;
case DAMAGE_ELEMENTAL_ACID:
if(resistRoll > critResist + acidResist && !buff.hasBuff(defenderData.id, "criticalAcidEffect") && !buff.hasBuff(defenderData.id, "criticalImmunity"))
{
buff.applyBuff(defenderData.id, "criticalAcidEffect");
damageMod = 1.5f;
hitData.critDamage = 5; // 5: acid crit
return damageMod;
}
break;
case DAMAGE_ELEMENTAL_ELECTRICAL:
if(resistRoll > critResist + electricResist && !buff.hasBuff(defenderData.id, "criticalElectricEffect") && !buff.hasBuff(defenderData.id, "criticalImmunity"))
{
buff.applyBuff(defenderData.id, "criticalElectricEffect");
damageMod = 1.5f;
hitData.critDamage = 6; // 6: electric crit
return damageMod;
}
break;
}
}
// sendSystemMessageTestingOnly(attackerData.id, "specialEffectChance " + specialEffectChance + ". hitData.critDamage " + hitData.critDamage);
return damageMod;
}
color getCriticalColor(int hitDataCritDamage)
{
color criticalColor = colors.MEDIUMVIOLETRED;
switch(hitDataCritDamage)
{
case 1:
criticalColor = colors.SLATEGREY;
break;
case 3:
criticalColor = colors.ORANGERED;
break;
case 4:
criticalColor = colors.LIGHTSKYBLUE;
break;
case 5:
criticalColor = colors.DARKOLIVEGREEN;
break;
case 6:
criticalColor = colors.SILVER;
break;
}
return criticalColor;
}
obj_id directDamageToDifferentTarget(obj_id attacker, obj_id defender)
{
if(buff.hasBuff(defender, "bm_shield_master_player"))
{
obj_id guardian = beast_lib.getBeastOnPlayer(defender);
if(isIdValid(guardian) && exists(guardian) && !isDead(guardian) && !isIncapacitated(guardian) &&
getEnhancedSkillStatisticModifierUncapped(defender, "damage_immune") <= 0) // Stasis
{
return guardian;
}
}
if(utils.hasScriptVar(defender, DAMAGE_REDIRECT))
{
obj_id guardian = utils.getObjIdScriptVar(defender, DAMAGE_REDIRECT);
// Non-mobile guardians (barricades) should return before checking isDead, incap, and pvpCanAttack
if(isIdValid(guardian) && exists(guardian) && !isMob(guardian) &&
getEnhancedSkillStatisticModifierUncapped(defender, "damage_immune") <= 0)
{
return guardian;
}
if(isIdValid(guardian) && exists(guardian) && isMob(guardian) && !isDead(guardian) && !isIncapacitated(guardian) &&
getEnhancedSkillStatisticModifierUncapped(defender, "damage_immune") <= 0 && // Stasis
pvpCanAttack(attacker, guardian)) // Cannot use damage redirection on a target that the attacker cannot attack.
{
return guardian;
}
else
{
utils.removeScriptVar(defender, DAMAGE_REDIRECT);
}
}
return defender;
}
float getMissChance(attacker_data attackerData, defender_data defenderData, combat_data actionData, boolean autoAim)
{
obj_id attacker = attackerData.id;
obj_id defender = defenderData.id;
float missChance = isPlayer(attacker) ? 0.0f : 5.0f;
if (autoAim && isRangedWeapon(getCurrentWeapon(attacker)))
{
missChance += 5.0f; // 1 on a d20 is always a miss
boolean attackerMovePenalty = (getLocomotion(attacker) == LOCOMOTION_RUNNING && isRangedWeapon(getCurrentWeapon(attacker)));
boolean defenderMovePenalty = (getLocomotion(defender) == LOCOMOTION_RUNNING && isRangedWeapon(getCurrentWeapon(defender)));
if (isPlayer(attacker) && attackerMovePenalty)
missChance += 5.0f;
if (defenderMovePenalty)
missChance += 3.0f;
}
if(isRangedWeapon(getCurrentWeapon(attackerData.id)))
{
missChance += ((float)getEnhancedSkillStatisticModifierUncapped(defenderData.id, "combat_ranged_attack_avoidance") / 10.0f);
missChance -= ((float)getEnhancedSkillStatisticModifierUncapped(defenderData.id, "combat_ranged_attack_vulnerability") / 10.0f);
}
if(isMeleeWeapon(getCurrentWeapon(attackerData.id)) || isLightsaberWeapon(getCurrentWeapon(attackerData.id)))
{
missChance += ((float)getEnhancedSkillStatisticModifierUncapped(defenderData.id, "combat_melee_attack_avoidance") / 10.0f);
missChance -= ((float)getEnhancedSkillStatisticModifierUncapped(defenderData.id, "combat_melee_attack_vulnerability") / 10.0f);
}
missChance += getAttackerMissChance(attackerData, actionData, autoAim);
missChance += getDefenderMissChance(defenderData, actionData, autoAim);
missChance -= attackerData.hitChance;
missChance += defenderData.increaseMiss;
return missChance;
}
float getAttackerMissChance(attacker_data attackerData, combat_data actionData, boolean autoAim)
{
float attackerMissChance = 0.0f;
if (isRangedWeapon(getCurrentWeapon(attackerData.id)))
{
attackerMissChance += ((float)getEnhancedSkillStatisticModifierUncapped(attackerData.id, "combat_ranged_attack_miss") / 10.0f);
attackerMissChance -= ((float)getEnhancedSkillStatisticModifierUncapped(attackerData.id, "combat_ranged_attack_miss_reduction") / 10.0f);
}
if (isMeleeWeapon(getCurrentWeapon(attackerData.id)) || isLightsaberWeapon(getCurrentWeapon(attackerData.id)))
{
attackerMissChance += ((float)getEnhancedSkillStatisticModifierUncapped(attackerData.id, "combat_melee_attack_miss") / 10.0f);
attackerMissChance -= ((float)getEnhancedSkillStatisticModifierUncapped(attackerData.id, "combat_melee_attack_miss_reduction") / 10.0f);
}
attackerMissChance += ((float)getEnhancedSkillStatisticModifierUncapped(attackerData.id, "combat_all_attack_miss") / 10.0f);
attackerMissChance -= ((float)getEnhancedSkillStatisticModifierUncapped(attackerData.id, "combat_all_attack_miss_reduction") / 10.0f);
return attackerMissChance;
}
float getDefenderMissChance(defender_data defenderData, combat_data actionData, boolean autoAim)
{
float defenderMissChance = 0.0f;
defenderMissChance += ((float)getEnhancedSkillStatisticModifierUncapped(defenderData.id, "combat_all_attack_avoidance") / 10.0f);
defenderMissChance -= ((float)getEnhancedSkillStatisticModifierUncapped(defenderData.id, "combat_all_attack_miss_vulnerability") / 10.0f);
return defenderMissChance;
}
float getGlancingBlowChance(attacker_data attackerData, defender_data defenderData, combat_data actionData)
{
obj_id attacker = attackerData.id;
obj_id defender = defenderData.id;
float glancingChance = 0.0f;
if(isMob(defender) && beast_lib.isBeast(defender))
glancingChance += (int)beast_lib.getBeastGlanceChance(defender);
// Players have a 5% chance per level to land a glancing blow on a NPC
if((isPlayer(attacker) || beast_lib.isBeast(attacker)) && (!isPlayer(defender) && !beast_lib.isBeast(defender)))
{
int attackerLevel = getLevel(attacker);
int defenderLevel = getLevel(defender);
int difficultyClass = getIntObjVar(defender, "difficultyClass");
if(attackerLevel < defenderLevel)
glancingChance = 5.0f * (float)(defenderLevel - attackerLevel);
switch (difficultyClass)
{
case 1:
if (glancingChance < 5.0f)
glancingChance = 5.0f;
break;
case 2:
if (glancingChance < 15.0f)
glancingChance = 15.0f;
}
}
//Mobs below the reduced player level have a 2% per level chance to land a glancing blow.
if ((!isPlayer(attacker) && !beast_lib.isBeast(attacker)) && (isPlayer(defender) || beast_lib.isBeast(defender)))
{
int attackerLevel = getLevel(attacker);
int defenderLevel = getLevel(defender);
defenderLevel = Math.round((float)defenderLevel * 0.75f);
if (attackerLevel < defenderLevel)
glancingChance = 2.0f * (float)(defenderLevel - attackerLevel);
}
glancingChance += defenderData.glancingChance;
glancingChance -= attackerData.reduceGlancing;
boolean isRangedAttacker = isRangedWeapon(getCurrentWeapon(attacker));
boolean isRangedDefender = isRangedWeapon(getCurrentWeapon(defender));
if(isRangedAttacker) // ranged weapon
{
glancingChance += (float)getEnhancedSkillStatisticModifierUncapped(defender, "expertise_glancing_blow_ranged");
if(armor.hasExpertiseArmorSetBonus(defender))
glancingChance += (float)getEnhancedSkillStatisticModifierUncapped(defender, "expertise_armorset_glancing_blow_ranged_" + armor.getExpertiseArmorSetId(defender));
}
if(!isRangedAttacker) // melee weapon
{
glancingChance += (float)getEnhancedSkillStatisticModifierUncapped(defender, "expertise_glancing_blow_melee");
if(armor.hasExpertiseArmorSetBonus(defender))
glancingChance += (float)getEnhancedSkillStatisticModifierUncapped(defender, "expertise_armorset_glancing_blow_melee_" + armor.getExpertiseArmorSetId(defender));
}
glancingChance -= actionData.reduceGlancing;
if(beast_lib.isBeast(defender))
{
LOG("beastDamage", "glancingChance: " + glancingChance);
}
return glancingChance;
}
float getDefenderGlancingBlowChance(obj_id defender)
{
float glancingChance = 0.0f;
boolean isRangedDefender = isRangedWeapon(getCurrentWeapon(defender));
if(!isRangedDefender)
{
glancingChance += (float)getEnhancedSkillStatisticModifierUncapped(defender, "expertise_glancing_blow_melee_defense");
}
glancingChance += (float)getEnhancedSkillStatisticModifierUncapped(defender, "expertise_glancing_blow_all");
glancingChance += (float)utils.getIntScriptVar(defender, "expertise_stance_glance");
glancingChance += (getDiminishedReturnValue((float)getEnhancedSkillStatisticModifierUncapped(defender, "combat_glancing") , DR_DEFENDER_GLANCE) / 10.0f);
return glancingChance;
}
float getAttackerGlancingReduction(obj_id attacker)
{
float glancingReduce = 0.0f;
if (beast_lib.isBeastMaster(attacker) || beast_lib.isBeast(attacker))
glancingReduce += beast_lib.getBeastModGlancingReduction(attacker);
glancingReduce += (float)(getEnhancedSkillStatisticModifierUncapped(attacker, "combat_glancing_blow_reduction") / 7.0f);
glancingReduce += (float)(getEnhancedSkillStatisticModifierUncapped(attacker, "expertise_glancing_blow_reduction"));
glancingReduce -= (float)getEnhancedSkillStatisticModifierUncapped(attacker, "glancing_blow_vulnerable");
return glancingReduce;
}
float getPunishingBlowChance(attacker_data attackerData, defender_data defenderData)
{
obj_id attacker = attackerData.id;
obj_id defender = defenderData.id;
float punishingChance = 0.0f;
// NPC have a 5% chance per level to land a punishing blow on a player
if ((!isPlayer(attacker) && !beast_lib.isBeast(attacker)) && (isPlayer(defender) || beast_lib.isBeast(defender)))
{
int attackerLevel = getLevel(attacker);
int defenderLevel = getLevel(defender);
if (attackerLevel > defenderLevel)
punishingChance = 5.0f * (float)(attackerLevel - defenderLevel);
int difficultyClass = getIntObjVar(attacker, "difficultyClass");
switch (difficultyClass)
{
case 1:
if (punishingChance < 5.0f)
punishingChance = 5.0f;
break;
case 2:
if (punishingChance < 15.0f)
punishingChance = 15.0f;
}
}
punishingChance -= defenderData.reducePunishing;
return punishingChance;
}
float getDefenderPunishingBlowReduction(obj_id defender)
{
float punishingChance = 0.0f;
if (beast_lib.isBeastMaster(defender) || beast_lib.isBeast(defender))
punishingChance += beast_lib.getBeastModPunishingReduction(defender);
return punishingChance;
}
float getDodgeChance(attacker_data attackerData, defender_data defenderData, combat_data actionData)
{
obj_id attacker = attackerData.id;
obj_id defender = defenderData.id;
float dodgeChance = 0.0f;
dodgeChance += defenderData.dodgeChance;
dodgeChance -= attackerData.reduceDodge;
dodgeChance -= actionData.reduceDodge;
boolean isRangedAttacker = isRangedWeapon(getCurrentWeapon(attacker));
if(isRangedAttacker) // ranged weapon
{
if(armor.hasExpertiseArmorSetBonus(defender))
dodgeChance += (float)getEnhancedSkillStatisticModifierUncapped(defender, "expertise_armorset_ranged_dodge_" + armor.getExpertiseArmorSetId(defender));
}
if(!isRangedAttacker) // melee weapon
{
if(armor.hasExpertiseArmorSetBonus(defender))
dodgeChance += (float)getEnhancedSkillStatisticModifierUncapped(defender, "expertise_armorset_melee_dodge_" + armor.getExpertiseArmorSetId(defender));
}
return dodgeChance;
}
float getDefenderDodgeChance(obj_id player)
{
float dodgeChance = isPlayer(player) || beast_lib.isBeast(player)? 5.0f : (float)getLevel(player) * 0.10f;
dodgeChance += ((float)getEnhancedSkillStatisticModifierUncapped(player, "agility_modified") / 100.0f);
dodgeChance += ((float)getEnhancedSkillStatisticModifierUncapped(player, "agility") / 100.0f);
dodgeChance += ((float)getEnhancedSkillStatisticModifierUncapped(player, "luck_modified")/ 300.0f);
dodgeChance += ((float)getEnhancedSkillStatisticModifierUncapped(player, "luck") / 300.0f);
dodgeChance += (getDiminishedReturnValue((float)getEnhancedSkillStatisticModifierUncapped(player, "combat_dodge"), DR_DEFENDER_DODGE) / 10.0f);
dodgeChance += (float)getEnhancedSkillStatisticModifierUncapped(player, "expertise_dodge");
if (beast_lib.isBeastMaster(player) || beast_lib.isBeast(player))
dodgeChance += beast_lib.getBeastDodgeChance(player);
return dodgeChance;
}
float getAttackerDodgeReduction(obj_id attacker)
{
float dodgeReduction = 0.0f;
dodgeReduction += (float)(getEnhancedSkillStatisticModifierUncapped(attacker, "expertise_dodge_reduction"));
//Exotic Mod
dodgeReduction += (float)(getEnhancedSkillStatisticModifierUncapped(attacker, "exotic_dodge_reduction") / 7.0f);
dodgeReduction += (float)(getEnhancedSkillStatisticModifierUncapped(attacker, "combat_dodge_reduction") / 7.0f);
return dodgeReduction;
}
float getParryChance(attacker_data attackerData, defender_data defenderData, combat_data actionData)
{
float parryChance = 0.0f;
parryChance += defenderData.parryChance;
parryChance -= attackerData.reduceParry;
parryChance -= actionData.reduceParry;
return parryChance;
}
float getDefenderParryChance(obj_id player)
{
float parrySkill = isPlayer(player) || beast_lib.isBeast(player) ? 5.0f : (float)getLevel(player) * 0.09f;
parrySkill += (getDiminishedReturnValue((float)getEnhancedSkillStatisticModifierUncapped(player, "combat_parry"),DR_DEFENDER_PARRY) / 10.0f);
parrySkill += ((float)getEnhancedSkillStatisticModifierUncapped(player, "precision_modified") / 200.0f );
parrySkill += ((float)getSkillStatisticModifier(player, "precision") / 200.0f);
parrySkill += ((float)getEnhancedSkillStatisticModifierUncapped(player, "agility_modified") / 200.0f);
parrySkill += ((float)getSkillStatisticModifier(player, "agility") / 200.0f);
parrySkill += (float)getEnhancedSkillStatisticModifierUncapped(player, "expertise_parry");
if (beast_lib.isBeastMaster(player) || beast_lib.isBeast(player))
parrySkill += beast_lib.getBeastParryChance(player);
parrySkill+= (float)getSkillStatisticModifier(player, "expertise_stance_saber_block");
boolean isSaberBlocking = buff.hasBuff(player, "saberBlock");
if (isSaberBlocking)
{
float enhancedSaberBlockSkillMod = (float)getSkillStatisticModifier(player, "expertise_saber_block");
parrySkill += enhancedSaberBlockSkillMod;
}
parrySkill = parrySkill > 75.0f ? 75.0f : parrySkill;
return parrySkill;
}
float getAttackerParryReduction(obj_id attacker)
{
float parryReduction = 0.0f;
parryReduction += (float)(getEnhancedSkillStatisticModifierUncapped(attacker, "expertise_parry_reduction"));
//Exotic Mod
parryReduction += (float)(getEnhancedSkillStatisticModifierUncapped(attacker, "exotic_parry_reduction") / 7.0f);
parryReduction += (float)(getEnhancedSkillStatisticModifierUncapped(attacker, "combat_parry_reduction") / 7.0f);
return parryReduction;
}
float getBlockChance(attacker_data attackerData, defender_data defenderData, combat_data actionData)
{
float blockChance = 0.0f;
obj_id attacker = attackerData.id;
obj_id defender = defenderData.id;
blockChance += defenderData.blockChance;
blockChance -= attackerData.reduceBlock;
blockChance -= actionData.reduceBlock;
boolean isRangedAttacker = isRangedWeapon(getCurrentWeapon(attacker));
if(isRangedAttacker) // ranged weapon
{
if(armor.hasExpertiseArmorSetBonus(defender))
blockChance += (float)getEnhancedSkillStatisticModifierUncapped(defender, "expertise_armorset_ranged_block_" + armor.getExpertiseArmorSetId(defender));
}
if(!isRangedAttacker) // melee weapon
{
if(armor.hasExpertiseArmorSetBonus(defender))
blockChance += (float)getEnhancedSkillStatisticModifierUncapped(defender, "expertise_armorset_melee_block_" + armor.getExpertiseArmorSetId(defender));
}
return blockChance;
}
float getDefenderBlockChance(obj_id player)
{
float blockChance = isPlayer(player) ? 5.0f : (float)getLevel(player) * 0.14f;
blockChance += ((float)getEnhancedSkillStatisticModifierUncapped(player, "precision_modified") / 200.0f );
blockChance += ((float)getEnhancedSkillStatisticModifierUncapped(player, "precision") / 200.0f);
blockChance += ((float)getEnhancedSkillStatisticModifierUncapped(player, "strength_modified") / 200.0f);
blockChance += ((float)getEnhancedSkillStatisticModifierUncapped(player, "strength") / 200.0f);
blockChance += (getDiminishedReturnValue((float)getEnhancedSkillStatisticModifierUncapped(player, "combat_block_chance"),DR_DEFENDER_BLOCK) / 5.0f);
blockChance += (float)getEnhancedSkillStatisticModifierUncapped(player, "expertise_block_chance");
if (beast_lib.isBeastMaster(player) || beast_lib.isBeast(player))
blockChance += beast_lib.getBeastBlockChance(player);
if (blockChance > 30.0f)
{
int nextBlockLog = utils.getIntScriptVar(player, "nextBlockLog");
if (getGameTime() > nextBlockLog)
{
float fromBlockChance = (float)getEnhancedSkillStatisticModifierUncapped(player, "combat_block_chance") / 5.0f;
float fromBlockChanceDiminished = (getDiminishedReturnValue((float)getEnhancedSkillStatisticModifierUncapped(player, "combat_block_chance"),DR_DEFENDER_BLOCK) / 5.0f);
float fromPreMod = (float)getEnhancedSkillStatisticModifierUncapped(player, "precision_modified") / 200.0f;
float fromPre = (float)getEnhancedSkillStatisticModifierUncapped(player, "precision") / 200.0f;
float fromStrMod = (float)getEnhancedSkillStatisticModifierUncapped(player, "strength_modified") / 200.0f;
float fromStr = (float)getEnhancedSkillStatisticModifierUncapped(player, "strength") / 200.0f;
float fromExChance = (float)getEnhancedSkillStatisticModifierUncapped(player, "expertise_block_chance");
float fromBeastMod = 0.0f;
if (beast_lib.isBeastMaster(player) || beast_lib.isBeast(player))
fromBeastMod += beast_lib.getBeastBlockChance(player);
utils.setScriptVar(player, "nextBlockLog", getGameTime() + 600);
CustomerServiceLog("BLOCK_CHANCE", "Player %TU total block chance: 5% + "+blockChance+
" = combat_block_chance("+fromBlockChance+ "/ DRF: "+fromBlockChanceDiminished+
") + precision_modified("+fromPreMod+
") + precision("+fromPre+
") + strength_modified("+fromStrMod+
") + strength("+fromStr+
") + Expertise Block("+fromExChance+
") + beast_mod("+fromBeastMod+")", player);
}
}
return blockChance;
}
float getAttackerBlockReduction(obj_id attacker)
{
float blockReduction = 0.0f;
blockReduction += (float)(getEnhancedSkillStatisticModifierUncapped(attacker, "expertise_block_reduction"));
//Exotic Mod
blockReduction += (float)(getEnhancedSkillStatisticModifierUncapped(attacker, "exotic_block_reduction") / 5.0f);
blockReduction += (float)(getEnhancedSkillStatisticModifierUncapped(attacker, "combat_block_reduction") / 5.0f);
return blockReduction;
}
int getBlockAmmount(obj_id player)
{
int blockAmmount = isPlayer(player) || beast_lib.isBeast(player) ? 25 : Math.round(getLevel(player) * 2);
float blockStat = 0.0f;
blockStat += (float)getEnhancedSkillStatisticModifierUncapped(player, "strength_modified");
blockStat += (float)getSkillStatisticModifier(player, "strength");
blockStat += (float)getEnhancedSkillStatisticModifierUncapped(player, "combat_block_value");
blockStat = Math.round((float)blockStat * 0.5f);
blockAmmount += blockStat;
if (beast_lib.isBeastMaster(player) || beast_lib.isBeast(player))
blockAmmount += beast_lib.getBeastBlockValue(player);
return blockAmmount;
}
float getEvasionChance(attacker_data attackerData, defender_data defenderData)
{
float evasionChance = 0.0f;
evasionChance += defenderData.evadeChance;
evasionChance -= attackerData.reduceEvade;
return evasionChance;
}
float getDefenderEvasionChance(obj_id player)
{
float evasionChance = isPlayer(player) || beast_lib.isBeast(player) ? 5.0f : ((float)getLevel(player) * 0.2f);
evasionChance += ((float)getEnhancedSkillStatisticModifierUncapped(player, "agility_modified") / 100.0f);
evasionChance += ((float)getSkillStatisticModifier(player, "agility") / 100.0f);
evasionChance += ((float)getEnhancedSkillStatisticModifierUncapped(player, "luck_modified")/ 300.0f);
evasionChance += ((float)getSkillStatisticModifier(player, "luck") / 300.0f);
evasionChance += (getDiminishedReturnValue((float)getEnhancedSkillStatisticModifierUncapped(player, "combat_evasion_chance"), DR_DEFENDER_EVADE) / 10.0f );
evasionChance += (float)getEnhancedSkillStatisticModifierUncapped(player, "expertise_evasion_chance");
if (beast_lib.isBeastMaster(player) || beast_lib.isBeast(player))
evasionChance += beast_lib.getBeastEvadeChance(player);
return evasionChance;
}
float getAttackerEvasionReduction(obj_id attacker)
{
return 0.0f;
}
float getHitReductionChance(obj_id player)
{
return getEnhancedSkillStatisticModifierUncapped(player, "expertise_critical_hit_reduction");
}
float getPvPHitReductionChance(obj_id player)
{
return getEnhancedSkillStatisticModifierUncapped(player, "expertise_critical_hit_pvp_reduction");
}
float getEvasionRoll(obj_id player)
{
float evasionRoll = 0.0f;
// -1 is evade all damage
evasionRoll += ((float)getEnhancedSkillStatisticModifierUncapped(player, "combat_evasion_value") / 4.0f);
if (evasionRoll < 0)
return -1.0f;
evasionRoll += 10.0f;
evasionRoll += ((float)getEnhancedSkillStatisticModifierUncapped(player, "luck_modified")/ 10.0f);
evasionRoll += ((float)getSkillStatisticModifier(player, "luck") / 10.0f);
if (beast_lib.isBeastMaster(player) || beast_lib.isBeast(player))
evasionRoll += beast_lib.getBeastEvadeValue(player);
return evasionRoll > 99.0f ? 99.0f : evasionRoll;
}
float getCriticalHitChance(attacker_data attackerData, defender_data defenderData, combat_data actionData, boolean autoAiming)
{
obj_id attacker = attackerData.id;
obj_id defender = defenderData.id;
float critChance = attackerData.critChance;
float altCritChance = 0.0f;
string specialLine = actionData.specialLine;
critChance += (float)getEnhancedSkillStatisticModifierUncapped(attacker, "expertise_critical_line_" + specialLine);
// +3% for manual aim
if(!autoAiming && isPlayer(attacker))
{
critChance += 3.0f;
}
// Defender niche mods
float specificDefenderMod = 0.0f;
if(isPlayer(defender) ) // Criticals against PCs
{
altCritChance += attackerData.pvpCrit;
}
else if(ai_lib.isDroid(defender) || ai_lib.isAndroid(defender)) // Crits against Droids and Android
{
altCritChance += attackerData.droidCrit;
}
else if(ai_lib.isNpc(defender)) // NPC
{
altCritChance += attackerData.npcCrit;
}
else if(ai_lib.isMonster(defender)) // Monster
{
altCritChance += attackerData.creatureCrit;
}
altCritChance = getDiminishedReturnValue(altCritChance, DR_ATTACKER_CRITICAL_2);
critChance -= defenderData.reduceCritical;
if(isPlayer(defender) &&
(isPlayer(attacker) || (isMob(attacker) && isIdValid(getMaster(attacker)) && isPlayer(getMaster(attacker)))))
{
critChance -= (float)getEnhancedSkillStatisticModifierUncapped(defender, "expertise_critical_hit_pvp_reduction");
}
critChance += actionData.increaseCritical;
critChance += altCritChance;
return critChance;
}
float getAttackerCritMod(obj_id attacker)
{
float critMod = isPlayer(attacker) ? 5.0f : (float)getLevel(attacker) * 0.15f;
float dr_critMod = 0.0f;
float dr_critMod2 = 0.0f;
critMod += (float)utils.getIntScriptVar(attacker, "expertise_stance_critical");
critMod += (float)getEnhancedSkillStatisticModifierUncapped(attacker, "expertise_critical_niche_all");
critMod += (getDiminishedReturnValue((float)getEnhancedSkillStatisticModifierUncapped(attacker, "combat_critical_hit"), DR_ATTACKER_CRITICAL) / 10.0f);
if (beast_lib.isBeastMaster(attacker) || beast_lib.isBeast(attacker))
critMod += beast_lib.getBeastCritChance(attacker);
// Get stat and weapon cat bonuses
critMod += ((float)getEnhancedSkillStatisticModifierUncapped(attacker, "precision_modified") / 100.0f );
critMod += ((float)getSkillStatisticModifier(attacker, "precision") / 100.0f);
critMod += ((float)getEnhancedSkillStatisticModifierUncapped(attacker, "luck_modified") / 300.0f);
critMod += ((float)getSkillStatisticModifier(attacker, "luck") / 300.0f);
obj_id weapon = getCurrentWeapon(attacker);
if (!isIdValid(weapon))
return critMod;
weapon_data weaponData = weapons.getNewWeaponData(weapon);
if (weaponData == null)
{
CustomerServiceLog ("COMBAT_exception", "getAttackerCritMod: Player %TU caused an exception with bad cached data on weapon "+weapon+"/"+getTemplateName(weapon), attacker);
return critMod;
}
boolean isRangedAttacker = isRangedWeapon(weapon);
if(isRangedAttacker) // ranged weapon, use precision mods
{
dr_critMod2 += (float)getEnhancedSkillStatisticModifierUncapped(attacker, "expertise_critical_ranged");
dr_critMod += ((float)getEnhancedSkillStatisticModifierUncapped(attacker, "combat_critical_ranged")/10.0f);
critMod += (float)getEnhancedSkillStatisticModifierUncapped(attacker, "expertise_undiminished_critical_ranged");
}
else
{
dr_critMod2 += (float)getEnhancedSkillStatisticModifierUncapped(attacker, "expertise_critical_melee");
dr_critMod += ((float)getEnhancedSkillStatisticModifierUncapped(attacker, "combat_critical_melee")/10.0f);
critMod += (float)getEnhancedSkillStatisticModifierUncapped(attacker, "expertise_undiminished_critical_melee");
}
// Weapon stat bonuses
switch(weaponData.weaponType)
{
case WEAPON_TYPE_PISTOL:
dr_critMod2 += (float)getEnhancedSkillStatisticModifierUncapped(attacker, "expertise_critical_pistol");
critMod += (float)getEnhancedSkillStatisticModifierUncapped(attacker, "expertise_undiminished_critical_pistol");
break;
case WEAPON_TYPE_RIFLE:
dr_critMod2 += (float)getEnhancedSkillStatisticModifierUncapped(attacker, "expertise_critical_rifle");
critMod += (float)getEnhancedSkillStatisticModifierUncapped(attacker, "expertise_undiminished_critical_rifle");
break;
case WEAPON_TYPE_LIGHT_RIFLE:
dr_critMod2 += (float)getEnhancedSkillStatisticModifierUncapped(attacker, "expertise_critical_carbine");
critMod += (float)getEnhancedSkillStatisticModifierUncapped(attacker, "expertise_undiminished_critical_carbine");
break;
case WEAPON_TYPE_HEAVY:
dr_critMod2 += (float)getEnhancedSkillStatisticModifierUncapped(attacker, "expertise_critical_heavy");
critMod += (float)getEnhancedSkillStatisticModifierUncapped(attacker, "expertise_undiminished_critical_heavy");
break;
case WEAPON_TYPE_GROUND_TARGETTING:
dr_critMod2 += (float)getEnhancedSkillStatisticModifierUncapped(attacker, "expertise_critical_heavy");
critMod += (float)getEnhancedSkillStatisticModifierUncapped(attacker, "expertise_undiminished_critical_heavy");
break;
case WEAPON_TYPE_DIRECTIONAL:
dr_critMod2 += (float)getEnhancedSkillStatisticModifierUncapped(attacker, "expertise_critical_heavy");
critMod += (float)getEnhancedSkillStatisticModifierUncapped(attacker, "expertise_undiminished_critical_heavy");
break;
case WEAPON_TYPE_1HAND_MELEE:
dr_critMod2 += (float)getEnhancedSkillStatisticModifierUncapped(attacker, "expertise_critical_1h");
critMod += (float)getEnhancedSkillStatisticModifierUncapped(attacker, "expertise_undiminished_critical_1h");
break;
case WEAPON_TYPE_2HAND_MELEE:
dr_critMod2 += (float)getEnhancedSkillStatisticModifierUncapped(attacker, "expertise_critical_2h");
critMod += (float)getEnhancedSkillStatisticModifierUncapped(attacker, "expertise_undiminished_critical_2h");
break;
case WEAPON_TYPE_UNARMED:
dr_critMod2 += (float)getEnhancedSkillStatisticModifierUncapped(attacker, "expertise_critical_unarmed");
critMod += (float)getEnhancedSkillStatisticModifierUncapped(attacker, "expertise_undiminished_critical_unarmed");
break;
case WEAPON_TYPE_POLEARM:
dr_critMod2 += (float)getEnhancedSkillStatisticModifierUncapped(attacker, "expertise_critical_polearm");
critMod += (float)getEnhancedSkillStatisticModifierUncapped(attacker, "expertise_undiminished_critical_polearm");
break;
case WEAPON_TYPE_WT_1HAND_LIGHTSABER:
case WEAPON_TYPE_WT_2HAND_LIGHTSABER:
case WEAPON_TYPE_WT_POLEARM_LIGHTSABER:
dr_critMod2 += (float)getEnhancedSkillStatisticModifierUncapped(attacker, "expertise_critical_lightsaber");
critMod += (float)getEnhancedSkillStatisticModifierUncapped(attacker, "expertise_undiminished_critical_lightsaber");
break;
}
dr_critMod = getDiminishedReturnValue(dr_critMod, DR_ATTACKER_CRITICAL);
dr_critMod2 = getDiminishedReturnValue(dr_critMod2, DR_ATTACKER_CRITICAL_2);
critMod += dr_critMod;
critMod += dr_critMod2;
return critMod;
}
float getDefenderCriticalChance(obj_id defender)
{
float critChance = 0.0f;
critChance -= (float)getEnhancedSkillStatisticModifierUncapped(defender, "critical_hit_vulnerable");
critChance += ((float)getEnhancedSkillStatisticModifierUncapped(defender, "combat_critical_hit_reduction") / 3.0f);
critChance += (float)getEnhancedSkillStatisticModifierUncapped(defender, "expertise_critical_hit_reduction");
return critChance;
}
float getStrikethroughChance(attacker_data attackerData, defender_data defenderData, combat_data actionData)
{
float strikethroughChance = 0.0f;
strikethroughChance += attackerData.strikethroughChance;
strikethroughChance -= defenderData.reduceStrikethrough;
strikethroughChance += actionData.increaseStrikethrough;
return strikethroughChance;
}
float getAttackerStrikethroughChance(obj_id player)
{
float strikethrough = isPlayer(player) || beast_lib.isBeast(player) ? 5.0f : (float)getLevel(player) * 0.08f;
strikethrough += ((float)getEnhancedSkillStatisticModifierUncapped(player, "precision_modified") / 200.0f );
strikethrough += ((float)getSkillStatisticModifier(player, "precision") / 200.0f);
strikethrough += ((float)getEnhancedSkillStatisticModifierUncapped(player, "luck_modified") / 200.0f);
strikethrough += ((float)getSkillStatisticModifier(player, "luck") / 200.0f);
strikethrough += (getDiminishedReturnValue((float)getEnhancedSkillStatisticModifierUncapped(player, "combat_strikethrough_chance"),DR_ATTACKER_STRIKETHROUGH) / 10.0f);
strikethrough += (float)getEnhancedSkillStatisticModifierUncapped(player, "expertise_strikethrough_chance");
if (beast_lib.isBeastMaster(player) || beast_lib.isBeast(player))
strikethrough += beast_lib.getBeastStrikethroughChance(player);
return strikethrough;
}
float getDefenderStrikethroughReduction(obj_id player)
{
float strikethroughChance = 0.0f;
strikethroughChance -= (float)getEnhancedSkillStatisticModifierUncapped(player, "strikethrough_vulnerable");
return strikethroughChance;
}
float getStrikethroughValue(obj_id attacker, obj_id defender)
{
return getAttackerStrikethroughValue(attacker);
}
float getAttackerStrikethroughValue(obj_id player)
{
float strikethroughValue = isPlayer(player) ? 10.0f : 35.0f;
strikethroughValue += ((float)getEnhancedSkillStatisticModifierUncapped(player, "luck_modified")/ 10.0f);
strikethroughValue += ((float)getSkillStatisticModifier(player, "luck") / 10.0f);
strikethroughValue += ((float)getEnhancedSkillStatisticModifierUncapped(player, "combat_strikethrough_value") / 2.0f);
if (beast_lib.isBeastMaster(player) || beast_lib.isBeast(player))
strikethroughValue += beast_lib.getBeastStrikethroughValue(player);
return strikethroughValue > 99.0f ? 99.0f : strikethroughValue;
}
float getToHitBonus(obj_id player)
{
float hitBonus = 0.0f;
hitBonus += ((float)getEnhancedSkillStatisticModifierUncapped(player, "strength_modified") / 100.0f);
hitBonus += ((float)getSkillStatisticModifier(player, "strength") / 100.0f);
hitBonus += (float)getEnhancedSkillStatisticModifierUncapped(player, "expertise_to_hit");
if (beast_lib.isBeastMaster(player) || beast_lib.isBeast(player))
hitBonus += beast_lib.getBeastHitChance(player);
return hitBonus;
}
float getDefenderHitReduction(obj_id player)
{
float increaseMiss = 0.0f;
return increaseMiss;
}
attacker_data fillAttackerCombatAttributes(attacker_data attackerData)
{
if(!utils.hasScriptVar(attackerData.id, ATTACKER_DATA))
cacheAttackerData(attackerData.id);
attackerData.critChance = utils.getFloatScriptVar(attackerData.id, ATTACKER_BASE_CRIT);
attackerData.pvpCrit = utils.getFloatScriptVar(attackerData.id, ATTACKER_PVP_CRIT);
attackerData.npcCrit = utils.getFloatScriptVar(attackerData.id, ATTACKER_NPC_CRIT);
attackerData.droidCrit = utils.getFloatScriptVar(attackerData.id, ATTACKER_DROID_CRIT);
attackerData.creatureCrit = utils.getFloatScriptVar(attackerData.id, ATTACKER_CREATURE_CRIT);
attackerData.hitChance = utils.getFloatScriptVar(attackerData.id, ATTACKER_HIT_BONUS);
attackerData.strikethroughChance = utils.getFloatScriptVar(attackerData.id, ATTACKER_STRIKETHROUGH);
attackerData.reduceParry = utils.getFloatScriptVar(attackerData.id, ATTACKER_REDUCE_PARRY);
attackerData.reduceBlock = utils.getFloatScriptVar(attackerData.id, ATTACKER_REDUCE_BLOCK);
attackerData.reduceDodge = utils.getFloatScriptVar(attackerData.id, ATTACKER_REDUCE_DODGE);
attackerData.reduceGlancing = utils.getFloatScriptVar(attackerData.id, ATTACKER_REDUCE_GLANCING);
attackerData.reduceEvade = utils.getFloatScriptVar(attackerData.id, ATTACKER_REDUCE_EVADE);
return attackerData;
}
defender_data fillDefenderCombatAttributes(defender_data defenderData)
{
if (!utils.hasScriptVar(defenderData.id, DEFENDER_DATA))
cacheDefenderData(defenderData.id);
defenderData.reduceCritical = utils.getFloatScriptVar(defenderData.id, DEFENDER_REDUCE_CRIT);
defenderData.increaseMiss = utils.getFloatScriptVar(defenderData.id, DEFENDER_INCREASE_MISS);
defenderData.reduceStrikethrough = utils.getFloatScriptVar(defenderData.id, DEFENDER_REDUCE_STRIKETHROUGH);
defenderData.reducePunishing = utils.getFloatScriptVar(defenderData.id, DEFENDER_REDUCE_PUNISH);
defenderData.glancingChance = utils.getFloatScriptVar(defenderData.id, DEFENDER_GLANCING);
defenderData.parryChance = utils.getFloatScriptVar(defenderData.id, DEFENDER_PARRY);
defenderData.dodgeChance = utils.getFloatScriptVar(defenderData.id, DEFENDER_DODGE);
defenderData.evadeChance = utils.getFloatScriptVar(defenderData.id, DEFENDER_EVADE);
defenderData.blockChance = utils.getFloatScriptVar(defenderData.id, DEFENDER_BLOCK);
return defenderData;
}
void cacheCombatData(obj_id self)
{
cacheAttackerData(self);
cacheDefenderData(self);
}
void clearCachedCombatData(obj_id self)
{
utils.removeScriptVarTree(self, ATTACKER_DATA);
utils.removeScriptVarTree(self, DEFENDER_DATA);
}
void cacheAttackerData(obj_id attacker)
{
utils.setScriptVar(attacker, ATTACKER_BASE_CRIT, getAttackerCritMod(attacker));
utils.setScriptVar(attacker, ATTACKER_PVP_CRIT, (float)getEnhancedSkillStatisticModifierUncapped(attacker, "expertise_critical_niche_pvp"));
utils.setScriptVar(attacker, ATTACKER_DROID_CRIT, (float)getEnhancedSkillStatisticModifierUncapped(attacker, "expertise_critical_niche_droid"));
utils.setScriptVar(attacker, ATTACKER_NPC_CRIT, (float)getEnhancedSkillStatisticModifierUncapped(attacker, "expertise_critical_niche_npc"));
utils.setScriptVar(attacker, ATTACKER_CREATURE_CRIT,(float)getEnhancedSkillStatisticModifierUncapped(attacker, "expertise_critical_niche_creature"));
utils.setScriptVar(attacker, ATTACKER_HIT_BONUS, getToHitBonus(attacker));
utils.setScriptVar(attacker, ATTACKER_STRIKETHROUGH, getAttackerStrikethroughChance(attacker));
utils.setScriptVar(attacker, ATTACKER_REDUCE_PARRY, getAttackerParryReduction(attacker));
utils.setScriptVar(attacker, ATTACKER_REDUCE_BLOCK, getAttackerBlockReduction(attacker));
utils.setScriptVar(attacker, ATTACKER_REDUCE_DODGE, getAttackerDodgeReduction(attacker));
utils.setScriptVar(attacker, ATTACKER_REDUCE_GLANCING, getAttackerGlancingReduction(attacker));
utils.setScriptVar(attacker, ATTACKER_REDUCE_EVADE, getAttackerEvasionReduction(attacker));
}
void cacheDefenderData(obj_id defender)
{
utils.setScriptVar(defender, DEFENDER_REDUCE_CRIT, getDefenderCriticalChance(defender));
utils.setScriptVar(defender, DEFENDER_INCREASE_MISS, getDefenderHitReduction(defender));
utils.setScriptVar(defender, DEFENDER_REDUCE_STRIKETHROUGH, getDefenderStrikethroughReduction(defender));
utils.setScriptVar(defender, DEFENDER_REDUCE_PUNISH, getDefenderPunishingBlowReduction(defender));
utils.setScriptVar(defender, DEFENDER_GLANCING, getDefenderGlancingBlowChance(defender));
utils.setScriptVar(defender, DEFENDER_PARRY, getDefenderParryChance(defender));
utils.setScriptVar(defender, DEFENDER_DODGE, getDefenderDodgeChance(defender));
utils.setScriptVar(defender, DEFENDER_EVADE, getDefenderEvasionChance(defender));
utils.setScriptVar(defender, DEFENDER_BLOCK, getDefenderBlockChance(defender));
}
obj_id getRandomHateTarget(obj_id self)
{
obj_id[] potentialTargets = getHateList(self);
if (potentialTargets == null || potentialTargets.length == 0)
return null;
float max_range = 64.0f;
resizeable obj_id[] validTargets = new obj_id[0];
for (int i=0;i<potentialTargets.length;i++)
{
if (isIdValid(potentialTargets[i]) && exists(potentialTargets[i]) && canSee(self, potentialTargets[i])
&& !stealth.hasInvisibleBuff(potentialTargets[i]) && getMaster(potentialTargets[i]) == null && !isDead(potentialTargets[i]))
{
if (getDistance(self, potentialTargets[i]) < max_range)
utils.addElement(validTargets, potentialTargets[i]);
}
}
if (validTargets == null || validTargets.length == 0)
return null;
return validTargets[rand(0, validTargets.length - 1)];
}
boolean doBhTaunt(obj_id player, obj_id target)
{
if(!isIdValid(player) || !isIdValid(target))
{
return false;
}
if(!exists(player) || !exists(target))
{
return false;
}
obj_id[] enemies = getHateList(target);
// Target hates nobody
if(enemies == null || enemies.length == 0)
{
return false;
}
obj_id topHateTarget = getHateTarget(target);
float topHateAmount = getHate(target, topHateTarget);
int level = getLevel(player);
float increasedHate = topHateAmount;
if(level <= BH_TAUNT_LOW_LEVEL)
{
increasedHate += BH_TAUNT_HATE_INCREASE_LOW;
}
else if(level <= BH_TAUNT_MID_LEVEL)
{
increasedHate += BH_TAUNT_HATE_INCREASE_MID;
}
else
{
increasedHate += BH_TAUNT_HATE_INCREASE_HIGH;
}
addHate(target, player, increasedHate);
return true;
}
boolean dsFsTaunt(obj_id player, obj_id target)
{
//Make sure they are in a stance
int stanceBuff = buff.getBuffOnTargetFromGroup(player, "fsStance");
if(stanceBuff == 0)
return false;
if(!isIdValid(player) || !isIdValid(target))
{
return false;
}
if(!exists(player) || !exists(target))
{
return false;
}
obj_id[] enemies = getHateList(target);
// Target hates nobody
if(enemies == null || enemies.length == 0)
{
return false;
}
obj_id topHateTarget = getHateTarget(target);
float topHateAmount = getHate(target, topHateTarget);
int level = getLevel(player);
float increasedHate = topHateAmount;
if(level <= FS_TAUNT_LOW_LEVEL)
{
increasedHate += FS_TAUNT_HATE_INCREASE_LOW;
}
else if(level <= FS_TAUNT_MID_LEVEL)
{
increasedHate += FS_TAUNT_HATE_INCREASE_MID;
}
else
{
increasedHate += FS_TAUNT_HATE_INCREASE_HIGH;
}
addHate(target, player, increasedHate);
return true;
}
boolean isStunned(obj_id target)
{
return utils.hasScriptVar(target, TEMP_COMBAT_BLOCK);
}
int[] getSuccessBasedSingleTargetActionCost(hit_result hitData, combat_data actionData, obj_id attacker, weapon_data weaponData)
{
boolean isSingleTargetAttack = actionData.hitType == -1 && actionData.attackType == combat.SINGLE_TARGET && actionData.commandType != combat.LEFT_CLICK_DEFAULT;
int[] freeShot = {0,0,0};
if (!isSingleTargetAttack)
return freeShot;
int freeMiss = getEnhancedSkillStatisticModifierUncapped(attacker, "freeshot_case_miss");
int freeDodge = getEnhancedSkillStatisticModifierUncapped(attacker, "freeshot_case_dodge");
int freeParry = getEnhancedSkillStatisticModifierUncapped(attacker, "freeshot_case_parry");
int freeCrit = getEnhancedSkillStatisticModifierUncapped(attacker, "freeshot_case_crit");
int freeStrike = getEnhancedSkillStatisticModifierUncapped(attacker, "freeshot_case_strikethrough");
boolean isFreeshot = false;
if (hitData.success == false)
{
if (freeMiss > 0)
isFreeshot = true;
if (hitData.dodge == true && freeDodge > 0)
isFreeshot = true;
if (hitData.parry == true && freeParry > 0)
isFreeshot = true;
}
if (hitData.success == true)
{
if (hitData.critical == true && freeCrit > 0)
isFreeshot = true;
if (hitData.strikethrough == true && freeStrike > 0)
isFreeshot = true;
}
if (isFreeshot)
{
prose_package pp = new prose_package();
pp = prose.setStringId(pp, new string_id("spam", "freeshot"));
showFlyTextPrivateProseWithFlags(attacker, attacker, pp, 1.5f, colors.LEMONCHIFFON, FLY_TEXT_FLAG_IS_FREESHOT );
return freeShot;
}
int[] shotCost = getActionCost(attacker, weaponData, actionData);
return shotCost;
}
void setPersistCombatMode(obj_id target, boolean state)
{
if (!isMob(target))
return;
if (!state)
{
if (utils.hasScriptVar(target, combat.PERSIST_COMBAT))
utils.removeScriptVar(target, combat.PERSIST_COMBAT);
return;
}
utils.setScriptVar(target, combat.PERSIST_COMBAT, 1);
return;
}
boolean isPersistCombatMode(obj_id self)
{
return utils.hasScriptVar(self, combat.PERSIST_COMBAT);
}
boolean isValidPersistCombat(obj_id self)
{
if (isInvulnerable(self))
return false;
if (!getCreatureCoverVisibility(self))
return false;
if (isDead(self))
return false;
obj_id[] hateList = getHateList(self);
if (hateList == null || hateList.length == 0)
return false;
boolean hasLos = true;
for (int i=0;i<hateList.length;i++)
{
hasLos |= canSee(self, hateList[i]);
}
if (!hasLos)
return false;
return true;
}
boolean isDamageImmune(obj_id attacker, obj_id defender, combat_data actionData)
{
boolean attackerImmune = getEnhancedSkillStatisticModifierUncapped(attacker, "damage_immune") > 0;
boolean defenderImmune = getEnhancedSkillStatisticModifierUncapped(defender, "damage_immune") > 0;
prose_package pp = new prose_package();
boolean isBeneficial = (actionData.hitType == NON_ATTACK || actionData.hitType == NON_DAMAGE_ATTACK) && pvpCanHelp(attacker, defender);
if (attackerImmune && !isBeneficial)
{
pp.stringId = new string_id("cbt_spam", "immune_hit_other");
pp.target.set(defender);
pp.other.set(new string_id("cmd_n", actionData.actionName));
sendCombatSpamMessageProse(attacker, pp);
pp.target.set(attacker);
pp.stringId = new string_id("cbt_spam", "immune_target_hit_other");
sendCombatSpamMessageProse(defender, pp);
}
if (defenderImmune && !isBeneficial)
{
pp.stringId = new string_id("cbt_spam", "immune_get_hit");
pp.target.set(attacker);
pp.other.set(new string_id("cmd_n", actionData.actionName));
sendCombatSpamMessageProse(defender, pp);
pp.target.set(defender);
pp.stringId = new string_id("cbt_spam", "immune_target_hit");
sendCombatSpamMessageProse(attacker, pp);
}
return (attackerImmune || defenderImmune) && !isBeneficial;
}
float getDiminishedReturnValue(float value, int modType)
{
if (value < 0.0f)
return value;
float[] seedList =
{
500.0f, // Crit
500.0f, // Strikethrough
500.0f, // Block
500.0f, // Dodge
500.0f, // Parry
500.0f, // Glancing
500.0f, // Evasion
50.0f // Crit2
};
// This block for testing only.
/*if (modType == DR_DEFENDER_BLOCK)
{
debugSpeakMsg(getSelf(), "Pre-Value: "+value+", Type: "+modType);
debugSpeakMsg(getSelf(), "Post-Value: "+value * ( 1.0f - (value / (value + seedList[DR_DEFENDER_BLOCK])))+", Type: "+modType);
}*/
switch (modType)
{
case DR_ATTACKER_CRITICAL:
return (value * ( 1.0f - (value / (value + seedList[DR_ATTACKER_CRITICAL]))));
case DR_ATTACKER_STRIKETHROUGH:
return (value * ( 1.0f - (value / (value + seedList[DR_ATTACKER_STRIKETHROUGH]))));
case DR_DEFENDER_BLOCK:
return (value * ( 1.0f - (value / (value + seedList[DR_DEFENDER_BLOCK]))));
case DR_DEFENDER_DODGE:
return (value * ( 1.0f - (value / (value + seedList[DR_DEFENDER_DODGE]))));
case DR_DEFENDER_PARRY:
return (value * ( 1.0f - (value / (value + seedList[DR_DEFENDER_PARRY]))));
case DR_DEFENDER_GLANCE:
return (value * ( 1.0f - (value / (value + seedList[DR_DEFENDER_GLANCE]))));
case DR_DEFENDER_EVADE:
return (value * ( 1.0f - (value / (value + seedList[DR_DEFENDER_EVADE]))));
case DR_ATTACKER_CRITICAL_2:
return (value * ( 1.0f - (value / (value + seedList[DR_ATTACKER_CRITICAL_2]))));
}
return value;
}
void mirrorArmor(obj_id commando)
{
obj_id[] haters = getHateList(commando);
if (haters != null || haters.length > 0)
{
for(int i=0;i<haters.length;i++)
{
float myHate = getHate(haters[i], commando);
if (myHate <= 0.0f)
continue;
myHate /= 2.0f;
setHate(haters[i], commando, myHate);
}
}
}
boolean setKillMeter(obj_id player, int value)
{
int current = getKillMeter(player);
int delta = value - current;
incrementKillMeter(player, delta);
return true;
}
boolean modifyKillMeter(obj_id player, int value)
{
int current = getKillMeter(player);
int delta = current + value;
if (delta < 0) delta = 0;
if (delta > 50) delta = 50;
setKillMeter(player, delta);
return true;
}
boolean canDrainKillMeter(obj_id player, int value)
{
if (!isPlayer(player))
return true;
int current = getKillMeter(player);
int delta = current - value;
return delta < 0 ? false : true;
}
boolean drainKillMeter(obj_id player, int value)
{
if (!isPlayer(player))
return true;
if (!canDrainKillMeter(player, value))
return false;
return modifyKillMeter(player, -1*value);
}
location getCommandGroundTargetLocation(string params)
{
// ground target. Location is passed in the params
string[] stringLocationParams = split(params, ' ');
if(stringLocationParams == null || stringLocationParams.length <= 0)
return null;
location eggLoc = new location();
boolean inCell = false;
//check to see if we are in a cell
if(isIdValid(utils.stringToObjId(stringLocationParams[3])))
{
inCell = true;
}
//if we are not in a cell, we need to use the first x y z
if(!inCell)
{
eggLoc.x = utils.stringToFloat(stringLocationParams[0]);
eggLoc.y = utils.stringToFloat(stringLocationParams[1]);
eggLoc.z = utils.stringToFloat(stringLocationParams[2]);
}
//if we are in a cell, we need to use the 2nd x y z
else
{
eggLoc.cell = utils.stringToObjId(stringLocationParams[3]);
eggLoc.x = utils.stringToFloat(stringLocationParams[4]);
eggLoc.y = utils.stringToFloat(stringLocationParams[5]);
eggLoc.z = utils.stringToFloat(stringLocationParams[6]);
}
return eggLoc;
}
float getDevastationChance(obj_id attacker)
{
// 2% base
float devastationBonus = 2.0f;
//Commando Super Devistation Bonus
devastationBonus += (float)getEnhancedSkillStatisticModifierUncapped(attacker, "commando_devastation");
devastationBonus += (float)getEnhancedSkillStatisticModifierUncapped(attacker, "expertise_devastation_bonus") / 10.0f;
return devastationBonus;
}
float getElementalPenetration(obj_id attacker, string damageString)
{
float elementalPenetrationAmount = 0.0f;
if ( isIdValid(attacker) && damageString != null && damageString.length() > 0 )
{
string elementalPenetrationSkillModName = "exotic_" + damageString + "_penetration";
float elementalPenetrationSkillMod = (float)getEnhancedSkillStatisticModifierUncapped(attacker, elementalPenetrationSkillModName);
//LOG("ARMOR_TEST", "getElementalPenetration elementalPenetrationSkillMod = " +elementalPenetrationSkillMod);
if ( elementalPenetrationSkillMod > MAX_RE_EXOTIC_ELEMENTAL_PENETRATION_SKILLMOD )
elementalPenetrationSkillMod = MAX_RE_EXOTIC_ELEMENTAL_PENETRATION_SKILLMOD;
elementalPenetrationAmount = 100.0f - (elementalPenetrationSkillMod * 2.5f);
elementalPenetrationAmount *= 0.01f;
}
//LOG("ARMOR_TEST", "getElementalPenetration elementalPenetrationAmount = " +elementalPenetrationAmount);
return elementalPenetrationAmount;
}
boolean hasPrescience(obj_id attacker, obj_id defender, combat_data actionData)
{
if (!buff.hasBuff(defender, "bh_prescience"))
return false;
obj_id owner = buff.getBuffCaster(defender, "bh_prescience");
if (attacker == owner)
{
prose_package pp = new prose_package();
pp.stringId = new string_id("cbt_spam", "bh_prescience_attacker");
pp.target.set(defender);
pp.other.set(new string_id("cmd_n", actionData.actionName));
sendCombatSpamMessageProse(attacker, pp);
pp.target.set(attacker);
pp.stringId = new string_id("cbt_spam", "bh_prescience_defender");
sendCombatSpamMessageProse(defender, pp);
return true;
}
return false;
}