/******************************************************************************* Script : cbt_ovr_atk_inc Name : Combat Override Attack Include Purpose : include script for combat override system (attack functions) Author : Quixsilver Modified : May 30, 2010 Modified By : Zunath on May 24, 2011 This file is licensed under the terms of the GNU GENERAL PUBLIC LICENSE (GPL) Version 2 *******************************************************************************/ /********************************** TODO *************************************** - Handle Grenades - Handle Silencer On/Off /******************************** INCLUDES ************************************/ #include "x0_i0_position" #include "cbt_char_inc" #include "cbt_constant" #include "gun_include" #include "dcy_include" /*************************** FUNCTION PROTOTYPES ******************************/ // This is the main entry point for attacking in the Combat Override System. // - oAttacker will attempt to attack oTarget void MainAttack(object oAttacker, object oTarget); // This is the firearm shot/ballistics handling function // - FireShot represents an individual projectile // - oAttacker fires a shot at oTarget after fDelay seconds // - iCurrentShot, iMaxShots, and iAnimation pass informational values void FireShot(object oAttacker, object oTarget, object oWeapon, int iCurrentShot, int iMaxShots, float fDelay, int iAnimation); // This is the firearm shot/ballistics handling function // - FireShot represents an individual projectile // - oAttacker fires a shot at oTarget after fDelay seconds // - iCurrentShot, iMaxShots, and iAnimation pass informational values void FireBullet(object oAttacker, object oTarget, object oWeapon, struct GunInfoStruct stGunInfo); // This is the firearm shot/ballistics handling function // - FireShot represents an individual projectile // - oAttacker fires a shot at oTarget after fDelay seconds // - iCurrentShot, iMaxShots, and iAnimation pass informational values void FireShotgun(object oAttacker, object oWeapon, struct GunInfoStruct stGunInfo); // Gets oAttacker's currently selected target // - returns OBJECT_INVALID if current target is invalid object GetTarget(object oAttacker); // Sets oAttacker's current target to oTarget void SetTarget(object oAttacker, object oTarget); // Reload oAttacker's weapons if needed // - if bDualReload is TRUE then both weapons will be reloaded if equipped void ReloadWeapons(object oAttacker, int bDualReload = FALSE); // Get whether or not oAttacker is currently allowed to issue Attack Commands int GetAttackAllowed(object oAttacker); // Set whether or not oAttacker is currently allowed to issue Attack Commands void SetAttackAllowed(object oAttacker, int bAttackAllowed); // Gets the number of shots that oAttacker is currently set to fire int GetNumberShotsToFire(object oAttacker); // Sets the number of shots that oAttacker is currently set to fire void SetNumberShotsToFire(object oAttacker, int iNumberOfShots); // Determines whether or not oTarget is under a Knockdown effect int GetKnockdownStatus(object oTarget); // Sets the Knockdown status of oTarget // - if iStatus is TRUE then fDuration must be supplied void SetKnockdownStatus(object oTarget, int iStatus, float fDuration = 0.0); // Determines whether or not oTarget is in a crouching position int GetCrouchStatus(object oTarget); // Sets the Crouching status of oTarget void SetCrouchStatus(object oTarget, int iStatus); // Returns oPlaceables Cover value // - returns: // 0 for no cover // 1 for partial cover // 2 for full cover int GetPlaceableCover(object oPlaceable); // Returns a numeric value for the amount of damage a single shot does to a target. // oAttacker = The creature attacking // oTarget = The creature being attacked // stGunInfo = The GunInfoStruct which holds all information pertaining to the weapon being used in this attack // iFirearmSkill = The upgrade level for oAttacker's skill. Make sure you get the right skill or else calculation will be incorrect. int CalculateDamage(object oAttacker, object oTarget, struct GunInfoStruct stGunInfo, int iFirearmSkill); // Recursive function which continuously deals damage to oTarget until a time specified in the initial call has passd. void IncendiaryDamage(object oTarget, int iDamage); /************************* FUNCTION IMPLEMENTATION ****************************/ int GetCrouchStatus(object oTarget) { return GetLocalInt(oTarget, CBT_OVR_SVAR_IS_CROUCHED); } void SetCrouchStatus(object oTarget, int iStatus) { SetLocalInt(oTarget, CBT_OVR_SVAR_IS_CROUCHED, iStatus); } int GetPlaceableCover(object oPlaceable) { return GetLocalInt(oPlaceable, CBT_OVR_SVAR_PLACEABLE_COVER_VALUE); } int GetKnockdownStatus(object oTarget) { return GetLocalInt(oTarget, CBT_OVR_SVAR_IS_KNOCKED_DOWN); } void SetKnockdownStatus(object oTarget, int iStatus, float fDuration = 0.0) { if (iStatus) { effect eKD = EffectKnockdown(); ApplyEffectToObject(DURATION_TYPE_TEMPORARY, eKD, oTarget, fDuration); SetLocalInt(oTarget, CBT_OVR_SVAR_IS_KNOCKED_DOWN, TRUE); DelayCommand(fDuration, AssignCommand(oTarget, SetLocalInt(oTarget, CBT_OVR_SVAR_IS_KNOCKED_DOWN, FALSE))); } else { // this removes the knockdown effect SetCommandable(TRUE, oTarget); } } int GetNumberShotsToFire(object oAttacker) { return GetLocalInt(oAttacker,CBT_OVR_SVAR_ATTACK_SHOTS_TO_FIRE); } void SetNumberShotsToFire(object oAttacker, int iNumberOfShots) { SetLocalInt(oAttacker, CBT_OVR_SVAR_ATTACK_SHOTS_TO_FIRE, iNumberOfShots); } object GetTarget(object oAttacker) { return GetLocalObject(oAttacker,CBT_OVR_SVAR_ATTACK_TARGET); } void SetTarget(object oAttacker, object oTarget) { SetLocalObject(oAttacker,CBT_OVR_SVAR_ATTACK_TARGET,oTarget); // Notify Target based on skill check??? } int GetAttackAllowed(object oAttacker) { return GetLocalInt(oAttacker,CBT_OVR_SVAR_ATTACK_ALLOWED); } void SetAttackAllowed(object oAttacker, int bAttackAllowed) { SetLocalInt(oAttacker,CBT_OVR_SVAR_ATTACK_ALLOWED,bAttackAllowed); } void MainAttack(object oAttacker, object oTarget) { object oPC = oAttacker; int iShots = GetLocalInt(oPC, CBT_OVR_SVAR_ATTACK_SHOTS_TO_FIRE); if (iShots < 1) iShots = 1; // Make sure there's ammo available object oMagazine = GetItemInSlot(INVENTORY_SLOT_ARROWS, oPC); int iMagazineSize = GetItemStackSize(oMagazine); if(iMagazineSize < iShots) { // No more bullets. Inform attacker. if(iMagazineSize == 0) { FloatingTextStringOnCreature(GUN_OUT_OF_AMMO_MESSAGE, oPC, FALSE); return; } // Shoot off the last round(s) else { iShots = iMagazineSize; } } float fNextAttackDelay; float fSoundDelay; float fShotDelay; float fAnimationLength; float fAnimationDelay; float fAnimationSpeed; float fDualDelay; int iCurrentShot; object oWeapon1 = GetItemInSlot(INVENTORY_SLOT_RIGHTHAND, oAttacker); object oWeapon2 = GetItemInSlot(INVENTORY_SLOT_LEFTHAND, oAttacker); struct GunInfoStruct stGun1Info = GUN_GetGunInfo(oWeapon1); struct GunInfoStruct stGun2Info = GUN_GetGunInfo(oWeapon2); int iAnimation; // Rifles if (stGun1Info.iGunType == GUN_TYPE_RIFLE || stGun1Info.iGunType == GUN_TYPE_INVALID) { iAnimation = CBT_OVR_ANIMATION_RIFLE; // Default to Rifle Animation if iAnimation is Invalid fNextAttackDelay = 0.8; fSoundDelay = 0.1; // 0.15 very decent fShotDelay = 0.35; //18 fAnimationLength = 0.1; // 0.158 very decent fAnimationDelay = 0.25; // 0.3 very decent fAnimationSpeed = 8.0; // 7.0 very decent } // Handguns else if (stGun1Info.iGunType == GUN_TYPE_HANDGUN && stGun2Info.iGunType == GUN_TYPE_INVALID) { iAnimation = CBT_OVR_ANIMATION_PISTOL; fNextAttackDelay = 0.5; fSoundDelay = 0.5; // 0.15 very decent fShotDelay = 0.7; //18 fAnimationLength = 0.5; // 0.158 very decent fAnimationDelay = 0.5; // 0.3 very decent fAnimationSpeed = 2.0; // 7.0 very decent } // Magnums else if (stGun1Info.iGunType == GUN_TYPE_MAGNUM && stGun2Info.iGunType == GUN_TYPE_INVALID) { iAnimation = CBT_OVR_ANIMATION_PISTOL; fNextAttackDelay = 0.8; fSoundDelay = 0.5; // 0.15 very decent fShotDelay = 0.7; //18 fAnimationLength = 0.5; // 0.158 very decent fAnimationDelay = 0.5; // 0.3 very decent fAnimationSpeed = 2.0; // 7.0 very decent } // Dual Handguns or Dual Magnums (but not 1 handgun, 1 magnum) else if (stGun1Info.iGunType == GUN_TYPE_HANDGUN && stGun2Info.iGunType == GUN_TYPE_HANDGUN || stGun1Info.iGunType == GUN_TYPE_MAGNUM && stGun2Info.iGunType == GUN_TYPE_MAGNUM) { iAnimation = CBT_OVR_ANIMATION_PISTOL_DUAL; fNextAttackDelay = 0.8; fSoundDelay = 0.5; // 0.15 very decent fShotDelay = 0.7; //18 fAnimationLength = 0.5; // 0.158 very decent fAnimationDelay = 0.5; // 0.3 very decent fAnimationSpeed = 2.0; // 7.0 very decent fDualDelay = 0.2; } // SMGs else if (stGun1Info.iGunType == GUN_TYPE_SMG && stGun2Info.iGunType == GUN_TYPE_INVALID) { iAnimation = CBT_OVR_ANIMATION_SMG; fNextAttackDelay = 0.8; fSoundDelay = 0.1; // 0.15 very decent fShotDelay = 0.35; //18 fAnimationLength = 0.1; // 0.158 very decent fAnimationDelay = 0.25; // 0.3 very decent fAnimationSpeed = 8.0; // 7.0 very decent } // Dual SMGs else if (stGun1Info.iGunType == GUN_TYPE_SMG && stGun2Info.iGunType == GUN_TYPE_SMG) { iAnimation = CBT_OVR_ANIMATION_SMG_DUAL; fNextAttackDelay = 0.8; fSoundDelay = 0.1; fShotDelay = 0.35; fAnimationLength = 0.1; fAnimationDelay = 0.25; fAnimationSpeed = 8.0; fDualDelay = 0.05; } // Assault Rifles (Rifle animation, SMG stats) else if (stGun1Info.iGunType == GUN_TYPE_ASSAULT_RIFLE) { iAnimation = CBT_OVR_ANIMATION_RIFLE; fNextAttackDelay = 0.8; fSoundDelay = 0.1; fShotDelay = 0.35; fAnimationLength = 0.1; fAnimationDelay = 0.25; fAnimationSpeed = 8.0; fDualDelay = 0.05; } // Shotguns else if (stGun1Info.iGunType == GUN_TYPE_SHOTGUN) { iAnimation = CBT_OVR_ANIMATION_SHOTGUN; fNextAttackDelay = 0.8; fSoundDelay = 0.80; fShotDelay = 1.0; if (iShots > 1) { fAnimationLength = 0.60 + iShots * 0.02; } else { fAnimationLength = 0.60; } fAnimationDelay = 1.0; fAnimationSpeed = 1.20; } AssignCommand(oPC,ClearAllActions(TRUE)); TurnToFaceObject(oTarget, oPC); fAnimationLength = fAnimationLength * iShots + fAnimationDelay; AssignCommand(oPC, PlayAnimation(iAnimation,fAnimationSpeed,fAnimationLength)); AssignCommand(oPC, SetCommandable(FALSE,oPC)); AssignCommand(oPC, DelayCommand(fAnimationLength+fNextAttackDelay,SetCommandable(TRUE,oPC))); // Loop for attack for (iCurrentShot = 1; iCurrentShot < iShots+1; iCurrentShot++) { // Out of ammo or target is dead - we're done here. if(iMagazineSize <= 0 || GetIsDead(oTarget)) { break; } // Reduce magazine bullets by 1 (or destroy the stack if it's the last bullet) else if(iMagazineSize == 1) { iMagazineSize = 0; DestroyObject(oMagazine); } else { iMagazineSize = iMagazineSize - 1; SetItemStackSize(oMagazine, iMagazineSize); } if (iAnimation == CBT_OVR_ANIMATION_PISTOL || iAnimation == CBT_OVR_ANIMATION_PISTOL_DUAL || iAnimation == CBT_OVR_ANIMATION_SHOTGUN) { // Each additional shot increases the sound delay if (iCurrentShot != 1) { fShotDelay += fSoundDelay; } } else { // Default Timings fShotDelay += fSoundDelay; } // Fire each shot DelayCommand(fShotDelay, FireShot(oPC, oTarget, oWeapon1, iCurrentShot, iShots, fShotDelay,iAnimation)); // Second Shot for dual-pistols/SMGs if (iAnimation == CBT_OVR_ANIMATION_PISTOL_DUAL || iAnimation == CBT_OVR_ANIMATION_SMG_DUAL) { // Again, we need to ensure there's bullets available for use. Otherwise the second shot won't take effect if(iMagazineSize <= 0) { break; } // Reduce magazine bullets by 1 (or destroy the stack if it's the last bullet) else if(iMagazineSize == 1) { iMagazineSize = 0; SetLocalInt(oAttacker, GUN_TEMP_PREVENT_AMMO_BOX_BUG, TRUE); DestroyObject(oMagazine); DelayCommand(0.1, DeleteLocalInt(oAttacker, GUN_TEMP_PREVENT_AMMO_BOX_BUG)); } else { iMagazineSize = iMagazineSize - 1; SetItemStackSize(oMagazine, iMagazineSize); } DelayCommand(fDualDelay + fShotDelay, FireShot(oPC, oTarget, oWeapon2, iCurrentShot, iShots, fShotDelay,iAnimation)); } } // Display "Low Ammo" message when bullets reach specified threshold int iMaxMagazineSize = FloatToInt(stGun1Info.iMagazineSize * GUN_LOW_AMMO_MESSAGE_DISPLAY_PERCENTAGE); if(iMaxMagazineSize < 1) iMaxMagazineSize = 1; // Update bullet count on the gun SetLocalInt(oWeapon1, GUN_MAGAZINE_BULLET_COUNT, iMagazineSize); if(iMagazineSize <= 0) { FloatingTextStringOnCreature(GUN_OUT_OF_AMMO_MESSAGE, oPC, FALSE); } else if(iMagazineSize <= iMaxMagazineSize && iMagazineSize > 0) { FloatingTextStringOnCreature(GUN_LOW_AMMO_MESSAGE, oPC, FALSE); } // Update gun name to reflect change in ammo currently chambered UpdateItemName(oWeapon1); // Fire durability system for gun DCY_RunItemDecay(oPC, oWeapon1); // Disabled auto-attack - There's not an easy way to stop this from firing infinitely because the NWNX OnAttack event only fires when combat initially starts //AssignCommand(oPC, DelayCommand(fAnimationLength+fNextAttackDelay+0.3,ActionAttack(oTarget))); } void FireShot(object oAttacker, object oTarget, object oWeapon, int iCurrentShot, int iMaxShots, float fDelay, int iAnimation) { location lLocation = GetLocation(oAttacker); effect eSound; int iShotsLeft = iMaxShots - iCurrentShot; int iSilenced = GetLocalInt(oAttacker,CBT_OVR_SVAR_ATTACK_SILENCED); struct GunInfoStruct stGunInfo = GUN_GetGunInfo(oWeapon); // Pistols if (iAnimation == CBT_OVR_ANIMATION_PISTOL || iAnimation == CBT_OVR_ANIMATION_PISTOL_DUAL) { if (iSilenced) { eSound = EffectVisualEffect(CBT_OVR_SOUND_SHOT_SILENCED_PISTOL); } else { eSound = EffectVisualEffect(CBT_OVR_SOUND_SHOT_HEAVY_PISTOL_9MM); } } // SMGs else if (iAnimation == CBT_OVR_ANIMATION_SMG || iAnimation == CBT_OVR_ANIMATION_SMG_DUAL) { if (iSilenced) { eSound = EffectVisualEffect(CBT_OVR_SOUND_SHOT_SILENCED_PISTOL); } else { eSound = EffectVisualEffect(CBT_OVR_SOUND_SHOT_SMG_762); } } // Shotguns else if (iAnimation == CBT_OVR_ANIMATION_SHOTGUN) { eSound = EffectVisualEffect(CBT_OVR_SOUND_SHOT_SHOTGUN_12G); } else { if (iSilenced) { eSound = EffectVisualEffect(CBT_OVR_SOUND_SHOT_SILENCED_RIFLE); } else { eSound = EffectVisualEffect(CBT_OVR_SOUND_SHOT_ASSAULT_RIFLE_762); } } ApplyEffectAtLocation(DURATION_TYPE_INSTANT,eSound,lLocation); // Fire shotgun blast if (iAnimation == CBT_OVR_ANIMATION_SHOTGUN) { FireShotgun(oAttacker, oWeapon, stGunInfo); } // Otherwise, fire normal bullet else { FireBullet(oAttacker, oTarget, oWeapon, stGunInfo); } } int CalculateDamage(object oAttacker, object oTarget, struct GunInfoStruct stGunInfo, int iFirearmSkill) { float fDistance = GetDistanceBetween(oAttacker, oTarget); if(fDistance == 0.0) return 0; iFirearmSkill = iFirearmSkill + 1; float fMultiplier = ((Random(30) + 40)) * 0.001 + (iFirearmSkill * 0.005); float fMaxRange; int iCriticalChance = 1 + Random(5) + FloatToInt(((iFirearmSkill) * 0.5)) + stGunInfo.iCriticalRating; int iChanceToHit = 100; // Each weapon has a "sweet spot". When a player is within this range they receive a 0.02 multiplier bonus. // "Near" range setting if(stGunInfo.iRangeSetting == IP_CONST_FIREARM_RANGE_NEAR) { fMaxRange = GUN_RANGE_NEAR_MAXIMUM; if(fDistance >= GUN_RANGE_NEAR_MINIMUM && fDistance <= GUN_RANGE_NEAR_MAXIMUM) { fMultiplier = fMultiplier + 0.02; } } // "Mid" range setting else if(stGunInfo.iRangeSetting == IP_CONST_FIREARM_RANGE_MID) { fMaxRange = GUN_RANGE_MID_MAXIMUM; if(fDistance >= GUN_RANGE_MID_MINIMUM && fDistance <= GUN_RANGE_MID_MAXIMUM) { fMultiplier = fMultiplier + 0.02; } } // "Far" range setting else if(stGunInfo.iRangeSetting == IP_CONST_FIREARM_RANGE_FAR) { fMaxRange = GUN_RANGE_FAR_MAXIMUM; if(fDistance >= GUN_RANGE_FAR_MINIMUM && fDistance <= GUN_RANGE_FAR_MAXIMUM) { fMultiplier = fMultiplier + 0.02; } // Rifles (weapons with the "Far" range setting) receive a penalty to accuracy if a target is too close. // 3% accuracy loss per half meter below minimum range else if(fDistance < GUN_RANGE_FAR_MINIMUM) { iChanceToHit = iChanceToHit - FloatToInt((3 * (fabs(fDistance - GUN_RANGE_FAR_MINIMUM) / 0.5))); } } // Multiplier falls off starting at max range + 2.5 meters at a rate of 0.005 per half meter outside of max range // Chance to head shot also decreases at a rate of 2% loss per half meter outside max range // Accuracy lost at a rate of 3% per half meter past max range fMaxRange = fMaxRange + 2.5; if(fDistance > fMaxRange) { fMaxRange = fabs(fMaxRange - fDistance); fMultiplier = fMultiplier - (0.005 * (fMaxRange / 0.5)); iCriticalChance = iCriticalChance - FloatToInt((2 * (fMaxRange / 0.5))); if(iCriticalChance < 0) iCriticalChance = 0; iChanceToHit = iChanceToHit - FloatToInt((3 * (fMaxRange / 0.5))); } // Critical hit (Head shot) rolls. Note that shotguns do not have a chance to head shot since their spread is powerful already. if(Random(100) <= iCriticalChance && stGunInfo.iGunType != GUN_TYPE_SHOTGUN) { fMultiplier = fMultiplier + 0.04; FloatingTextStringOnCreature("Critical hit!", oAttacker, FALSE); } // Enhanced ammunition grants an additional 0.015 to the multiplier. int iAmmunitionType = GetLocalInt(stGunInfo.oGun, GUN_TEMP_AMMO_LOADED_TYPE); if(iAmmunitionType == GUN_AMMUNITION_PRIORITY_ENHANCED) { fMultiplier = fMultiplier + 0.015; } // Convert durability to a decimal, then multiply that value with the Firepower // Finally, multiply that value with the multiplier. int iFirepower = FloatToInt(stGunInfo.iFirepower * (0.01 * stGunInfo.iDurability)); //SendMessageToPC(oAttacker, "DEBUG: iFirepower = " + IntToString(iFirepower)); // DEBUG int iDamage = FloatToInt(iFirepower * fMultiplier); // Attack missed - return zero damage if(Random(100) > iChanceToHit) return 0; // Always deal at least 1 damage when an attack hits if(iDamage <= 0) iDamage = 1; // Incendiary ammunition deals fire damage to the target over time. It also applies a nifty visual to the target. if(iAmmunitionType == GUN_AMMUNITION_PRIORITY_INCENDIARY) { ApplyEffectToObject(DURATION_TYPE_TEMPORARY, EffectVisualEffect(VFX_DUR_INFERNO_CHEST), oTarget, 3.0); int bCurrentlyOnFire = GetLocalInt(oTarget, "INCENDIARY_DAMAGE_COUNTER"); SetLocalInt(oTarget, "INCENDIARY_DAMAGE_COUNTER", 3); // We don't want multiple copies of this function firing at the same time, so ensure there's not another // one already going if(!bCurrentlyOnFire) DelayCommand(1.0, IncendiaryDamage(oTarget, Random(3) + 3)); } return iDamage; } void IncendiaryDamage(object oTarget, int iDamage) { // Exit when target is dead or invalid if(GetIsDead(oTarget) || !GetIsObjectValid(oTarget)) return; int iCounter = GetLocalInt(oTarget, "INCENDIARY_DAMAGE_COUNTER") - 1; ApplyEffectToObject(DURATION_TYPE_INSTANT, EffectDamage(iDamage, DAMAGE_TYPE_FIRE), oTarget); // Exit when the incendiary effect wears off if(iCounter <= 0) return; // Otherwise update the counter and call this function again in one second SetLocalInt(oTarget, "INCENDIARY_DAMAGE_COUNTER", iCounter); DelayCommand(1.0, IncendiaryDamage(oTarget, Random(3) + 3)); } // Formula found at: http://paulbourke.net/geometry/insidepoly/ float CalcPosition(vector vP1, vector vP2, vector vPoint) { float x = vPoint.x; float y = vPoint.y; float x0 = vP1.x; float y0 = vP1.y; float x1 = vP2.x; float y1 = vP2.y; float result = (y - y0) * (x1 - x0) - (x - x0) * (y1 - y0); //SendMessageToPC(GetFirstPC(), "DEBUG: result = " + FloatToString(result)); // DEBUG return result; } int IsPointInShape(vector vOrigin, vector vStraightPosition, vector vRightPosition, vector vLeftPosition, vector vPoint) { int bInShape = TRUE; // P1 = Origin // P2 = Left // P3 = Straight // P4 = Right // If < 0.0, right of line segment // If > 0.0, left of line segment // If = 0.0, on line segment float fResult = CalcPosition(vOrigin, vLeftPosition, vPoint); //SendMessageToPC(GetFirstPC(), "vOrigin, vLeftPosition result = " + FloatToString(fResult)); // DEBUG: if(fResult < 0.0) return FALSE; fResult = CalcPosition(vLeftPosition, vStraightPosition, vPoint); //SendMessageToPC(GetFirstPC(), "vLeftPosition, vStraightPosition result = " + FloatToString(fResult)); // DEBUG: if(fResult < 0.0) return FALSE; fResult = CalcPosition(vStraightPosition, vRightPosition, vPoint); //SendMessageToPC(GetFirstPC(), "vStraightPosition, vRightPosition result = " + FloatToString(fResult)); // DEBUG: if(fResult < 0.0) return FALSE; fResult = CalcPosition(vRightPosition, vOrigin, vPoint); //SendMessageToPC(GetFirstPC(), "vRightPosition, vOrigin result = " + FloatToString(fResult)); // DEBUG: if(fResult < 0.0) return FALSE; return TRUE; } void FireShotgun(object oAttacker, object oWeapon, struct GunInfoStruct stGunInfo) { vector vOrigin = GetPosition(oAttacker); location lOrigin = GetLocation(oAttacker); float fFacing = GetFacing(oAttacker); int iShotgunSkill = ADV_GetUpgradeLevel(oAttacker, ADV_ID_SHOTGUN_PROFICIENCY); float fRangeDistance = 7.5; //location lTargetLocation = GetLocation(oTarget); int bMiss = TRUE; effect eSparkEffect = EffectVisualEffect(VFX_COM_BLOOD_SPARK_LARGE, FALSE); effect eBloodEffect = EffectVisualEffect(VFX_COM_CHUNK_RED_SMALL , FALSE); vector vStraightPosition = GetChangedPosition(vOrigin, fRangeDistance, fFacing); vector vRightPosition = GetChangedPosition(vOrigin, fRangeDistance * 0.9, fFacing + 45); vector vLeftPosition = GetChangedPosition(vOrigin, fRangeDistance * 0.9, fFacing - 45); // DEBUG SECTION /* location lRightPos = Location(GetArea(oAttacker), vRightPosition, 0.0); location lLeftPos = Location(GetArea(oAttacker), vLeftPosition, 0.0); location lStraightPos = Location(GetArea(oAttacker), vStraightPosition, 0.0); object oTest = CreateObject(OBJECT_TYPE_PLACEABLE, "plc_solblue", lRightPos); DestroyObject(oTest, 2.0); oTest = CreateObject(OBJECT_TYPE_PLACEABLE, "plc_solgreen", lLeftPos); DestroyObject(oTest, 2.0); oTest = CreateObject(OBJECT_TYPE_PLACEABLE, "plc_solred", lStraightPos); DestroyObject(oTest, 2.0); */ // END DEBUG SECTION object oSphereTarget = GetFirstObjectInShape(SHAPE_SPHERE, fRangeDistance, lOrigin, TRUE, OBJECT_TYPE_CREATURE, vOrigin); while(GetIsObjectValid(oSphereTarget)) { if(oSphereTarget != oAttacker && !GetIsPC(oSphereTarget) && !GetIsDead(oSphereTarget) && !GetIsDead(oAttacker)) { if(IsPointInShape(vOrigin, vStraightPosition, vRightPosition, vLeftPosition, GetPosition(oSphereTarget))) { int iDamage = CalculateDamage(oAttacker, oSphereTarget, stGunInfo, iShotgunSkill); if(iDamage > 0) { ApplyEffectToObject(DURATION_TYPE_INSTANT, EffectDamage(iDamage, DAMAGE_TYPE_PIERCING), oSphereTarget); ApplyEffectToObject(DURATION_TYPE_INSTANT, eSparkEffect, oSphereTarget); ApplyEffectToObject(DURATION_TYPE_INSTANT, eBloodEffect, oSphereTarget); bMiss = FALSE; } } } oSphereTarget = GetNextObjectInShape(SHAPE_SPHERE, fRangeDistance, lOrigin, TRUE, OBJECT_TYPE_CREATURE, vOrigin); } // Enemies were out of range - display sparks randomly across the cone if(bMiss) { object oArea = GetArea(oAttacker); int iNumberOfSparks = Random(4) + 1; int iLoop; for(iLoop = 1; iLoop <= iNumberOfSparks; iLoop++) { fRangeDistance = Random(75) * 0.1; vector vSparkPosition = GetChangedPosition(vOrigin, fRangeDistance, GetFacing(oAttacker)); ApplyEffectAtLocation(DURATION_TYPE_INSTANT, eSparkEffect, Location(oArea, vSparkPosition, 0.0)); } } } void FireBullet(object oAttacker, object oTarget, object oWeapon, struct GunInfoStruct stGunInfo) { vector vPosition = GetPosition(oAttacker); location lTargetLocation = GetLocation(oTarget); int bMiss = FALSE; float fClosestDistance; float fStunDuration; // Get the correct skill, based on the type of firearm being used // Also determine how long the stun effect lasts, based on weapon int iSkill; switch(stGunInfo.iGunType) { case GUN_TYPE_HANDGUN: iSkill = ADV_GetUpgradeLevel(oAttacker, ADV_ID_HANDGUN_PROFICIENCY); fStunDuration = 0.5; break; case GUN_TYPE_RIFLE: iSkill = ADV_GetUpgradeLevel(oAttacker, ADV_ID_RIFLE_PROFICIENCY); fStunDuration = 1.5; break; case GUN_TYPE_SMG: iSkill = ADV_GetUpgradeLevel(oAttacker, ADV_ID_SMG_PROFICIENCY); fStunDuration = 0.2; break; case GUN_TYPE_ASSAULT_RIFLE: iSkill = ADV_GetUpgradeLevel(oAttacker, ADV_ID_SMG_PROFICIENCY); fStunDuration = 0.5; break; case GUN_TYPE_MAGNUM: iSkill = ADV_GetUpgradeLevel(oAttacker, ADV_ID_MAGNUM_PROFICIENCY); fStunDuration = 1.0; break; } // Gas canisters always take priority when targeting. Otherwise players will cry about not hitting them when they want to. string sTag = GetTag(oTarget); if(sTag != GUN_GAS_CANISTER_TAG) { object oCylinderTarget = GetFirstObjectInShape(SHAPE_SPELLCYLINDER, 16.0, lTargetLocation, TRUE, OBJECT_TYPE_CREATURE, vPosition); while(GetIsObjectValid(oCylinderTarget)) { // Found a valid target. See if it's closest to attacker if(!GetIsDead(oCylinderTarget) && !GetIsPC(oCylinderTarget) && oAttacker != oCylinderTarget && !GetIsDead(oAttacker)) { float fDistanceCheck = GetDistanceBetween(oCylinderTarget, oAttacker); if(fDistanceCheck < fClosestDistance || fClosestDistance == 0.0) { oTarget = oCylinderTarget; fClosestDistance = fDistanceCheck; } } oCylinderTarget = GetNextObjectInShape(SHAPE_SPELLCYLINDER, 16.0, lTargetLocation, TRUE, OBJECT_TYPE_CREATURE, vPosition); } } // Calculate damage int iDamage = CalculateDamage(oAttacker, oTarget, stGunInfo, iSkill); if(iDamage <= 0) bMiss = TRUE; effect eSparkEffect = EffectVisualEffect(VFX_COM_BLOOD_SPARK_LARGE, bMiss); if(iDamage > 0) { ApplyEffectToObject(DURATION_TYPE_TEMPORARY, EffectMovementSpeedDecrease(50), oTarget, fStunDuration); ApplyEffectToObject(DURATION_TYPE_INSTANT, EffectDamage(iDamage, DAMAGE_TYPE_PIERCING), oTarget); } ApplyEffectToObject(DURATION_TYPE_INSTANT, eSparkEffect, oTarget); } // Error checking //void main(){}