/***** INCLUDES ********************************************************/ include library.callable; include library.factions; include library.pclib; include library.stealth; include library.trace; include library.utils; include library.vehicle; include library.weapons; /***** CONSTANTS *******************************************************/ const string ALERT_VOLUME_NAME = "turretAggroVolume"; const float DEFAULT_TURRET_RANGE = 64.0f; const float RANGE_TOO_CLOSE = 10f; const string VAR_TURRET_BASE = "turret"; const string IS_ACTIVE = VAR_TURRET_BASE + ".isActive"; const string TARGETS = VAR_TURRET_BASE + ".targetList"; const string TURRET_FRIEND = VAR_TURRET_BASE + ".isFriend"; // set this on a potential target to make the turret ignore them. friends never get attacked const string IS_ENGAGED = VAR_TURRET_BASE + ".isTrackingTarget"; const string IS_OUTDOORS = VAR_TURRET_BASE + ".isOutdoors"; const string FACTION = VAR_TURRET_BASE + ".faction"; const string RANGE = VAR_TURRET_BASE + ".range"; const string MY_WEAPON = VAR_TURRET_BASE + ".myWeapon"; const int TYPE_BLOCK = 1; const int TYPE_DISH = 2; const int TYPE_TOWER = 3; const int SIZE_SMALL = 1; const int SIZE_MEDIUM = 2; const int SIZE_LARGE = 3; const int SIZE_MINI = 4; obj_id createTurret(location loc, float yaw, int minDamage, int maxDamage, float attackSpeed, float range) { return createTurret(loc, yaw, TYPE_BLOCK, SIZE_SMALL, DAMAGE_ENERGY, minDamage, maxDamage, range, attackSpeed, ""); } obj_id createTurret(location loc, float yaw, int type, int size, int damageType, int minDamage, int maxDamage, float range, float attackSpeed, string faction) { return createTurret(loc, yaw, TYPE_BLOCK, SIZE_SMALL, DAMAGE_ENERGY, minDamage, maxDamage, 10000, range, attackSpeed, ""); } obj_id createTurret(location loc, float yaw, int type, int size, int damageType, int minDamage, int maxDamage, int hitpoints, float range, float attackSpeed, string faction) { return createTurret(null, loc, yaw, type, size, damageType, minDamage, maxDamage, hitpoints, range, attackSpeed, faction); } obj_id createTurret(obj_id owner, location loc, float yaw, int type, int size, int damageType, int minDamage, int maxDamage, int hitpoints, float range, float attackSpeed, string faction) { string template = "object/installation/turret/gcw/adv_turret"; switch(type) { case TYPE_BLOCK: template += "_block"; break; case TYPE_DISH: template += "_dish"; break; case TYPE_TOWER: template += "_tower"; break; } switch(size) { case SIZE_MINI: template += "_mini"; break; case SIZE_SMALL: template += "_sm"; break; case SIZE_MEDIUM: if(type == TYPE_DISH) { template += "_sm"; // No medium dish exists } else { template += "_med"; } break; case SIZE_LARGE: template += "_large"; break; } switch(damageType) { case DAMAGE_ELEMENTAL_HEAT: template += "_heat"; break; case DAMAGE_ELEMENTAL_COLD: template += "_cold"; break; case DAMAGE_ELEMENTAL_ACID: template += "_acid"; break; case DAMAGE_ELEMENTAL_ELECTRICAL: template += "_electricity"; break; case DAMAGE_KINETIC: template += "_kinetic"; break; default: //DAMAGE_ENERGY template += "_energy"; break; } template += ".iff"; obj_id turret = createObject(template, loc); if(!isIdValid(turret)) { return null; } setYaw(turret, yaw); if(isIdValid(owner) && exists(owner)) { setObjVar(turret, "turret_master", owner); } initTurret(turret, faction, damageType, range, minDamage, maxDamage, hitpoints, attackSpeed); return turret; } void activateTurret(obj_id turret) { LOG("TURRET", "activateTurret " + turret); if(!isIdValid(turret)) { return; } obj_id turretWeapon = getTurretWeapon(turret); if (!isIdValid(turretWeapon)) return; utils.setScriptVar(turret, IS_ACTIVE, true); setInvulnerable(turret, false); pvpSetAttackableOverride(turret, true); weapons.setWeaponData(turretWeapon); float range = getFloatObjVar(turret, RANGE); dictionary dict = new dictionary(); dict.put("range", range); messageTo(turret, "createTriggerVolume", dict, 0.0f, false); return; } void deactivateTurret(obj_id turret) { LOG("TURRET", "deactivateTurret " + turret); if(!isIdValid(turret)) { return; } stopTrackingTarget(turret); utils.removeScriptVar(turret, IS_ACTIVE); setInvulnerable(turret, true); return; } boolean isActive(obj_id turret) { if(!isIdValid(turret)) { return false; } if(utils.hasScriptVar(turret, IS_ACTIVE)) { LOG("TURRET", "Turret " + turret + " is active."); return true; } LOG("TURRET", "Turret " + turret + " is not active."); return false; } // causes the muzzle to track the target. turret must be active before it can start tracking a target void startTrackingTarget(obj_id turret, obj_id target) { LOG("TURRET", "Turret " + turret + " start tracking target " + target); if(!isIdValid(turret) || !isIdValid(target)) { return; } if(!isActive(turret)) { LOG("TURRET", "Turret " + turret + " can't track target - turret inactive."); return; } utils.setScriptVar(turret, IS_ENGAGED, target); //setCombatTarget(turret, target); return; } // stops the turret muzzle from tracking the target void stopTrackingTarget(obj_id turret) { LOG("TURRET", "Turret " + turret + " stop tracking target."); if(!isIdValid(turret)) { return; } utils.removeScriptVar(turret, IS_ENGAGED); //setCombatTarget(turret, null); return; } boolean isEngaged(obj_id turret) { if(!isIdValid(turret)) { return false; } if(utils.hasScriptVar(turret, IS_ENGAGED)) { return true; } return false; } obj_id getCurrentTarget(obj_id turret) { if(!isEngaged(turret)) { return null; } return utils.getObjIdScriptVar(turret, IS_ENGAGED); } obj_id getTurretWeapon(obj_id turret) { if(!isIdValid(turret) || !exists(turret)) { return null; } if(!hasObjVar(turret, MY_WEAPON)) { return null; } return getObjIdObjVar(turret, MY_WEAPON); } // // use factions.FACTION_IMPERIAL, factions.FACTION_REBEL, or a string from datatables/faction/faction.iff // to set the faction of the turret. the turret does a factions.areFactionsOpposed test before attacking a target. // set the faction to "" (empty string) to make the turret attack every player regardless of faction // set the range to -1 to use DEFAULT_TURRET_RANGE void initTurret(obj_id turret, string faction, int damageType, float range, int minDamage, int maxDamage, int hitpoints, float attackSpeed) { detachScript(turret, "systems.turret.turret_ai"); attachScript(turret, "systems.turret.advanced_turret_ai"); boolean isOutdoors = getTopMostContainer(turret) == turret; setObjVar(turret, IS_OUTDOORS, isOutdoors); setObjVar(turret, FACTION, faction.equals("")? "renegade" :faction); // setting an empty string in local vars sucks, so we set "renegage" as the all killer faction setObjVar(turret, RANGE, range == -1f? DEFAULT_TURRET_RANGE : range); factions.setFaction(turret, faction); setMaxHitpoints(turret, hitpoints); setHitpoints(turret, hitpoints); setObjVar(turret, "pvpCanAttack", 1); setInvulnerable(turret, false); string weaponTemplate = "object/weapon/ranged/turret/turret_block_large.iff"; obj_id objWeapon = createObject(weaponTemplate, turret, ""); if(!isIdValid(objWeapon)) { explodeTurret(turret, turret); return; } setObjVar(turret, MY_WEAPON, objWeapon); setWeaponAttackSpeed(objWeapon, attackSpeed < 1f? 1f : attackSpeed); int primaryType = DAMAGE_ENERGY; int secondaryType = DAMAGE_ELEMENTAL_HEAT; int primaryMinDamage = 0; int primaryMaxDamage = 0; int secondaryDamage = 0; switch(damageType) { case DAMAGE_ELEMENTAL_COLD: case DAMAGE_ELEMENTAL_ACID: case DAMAGE_ELEMENTAL_ELECTRICAL: case DAMAGE_ELEMENTAL_HEAT: primaryType = DAMAGE_ENERGY; secondaryType = damageType; primaryMinDamage = 1; primaryMaxDamage = 1; secondaryDamage = (int)((float)minDamage + (float)maxDamage / 2.0f); break; case DAMAGE_KINETIC: case DAMAGE_ENERGY: primaryType = damageType; secondaryType = DAMAGE_ELEMENTAL_HEAT; primaryMinDamage = minDamage; primaryMaxDamage = maxDamage; secondaryDamage = 0; break; } setWeaponDamageType(objWeapon, primaryType); setWeaponElementalType(objWeapon, secondaryType); setWeaponMinDamage(objWeapon, minDamage); setWeaponMaxDamage(objWeapon, maxDamage); setWeaponElementalValue(objWeapon, secondaryDamage); range_info ri = new range_info(); ri.maxRange = range; setWeaponRangeInfo(objWeapon, ri); activateTurret(turret); return; } boolean isValidTarget(obj_id turret, obj_id target) { // Both objects must be valid and on the same game server if(!isIdValid(turret) || !isIdValid(target) || !exists(turret) || !exists(target) ) { doLogging("isValidTarget", "I or "+target+" is not valid or does not exist: "+isIdValid(turret)+"/"+isIdValid(target)+"/"+exists(turret)+"/"+exists(target)); return false; } if(vehicle.isVehicle(target)) { doLogging("isValidTarget", "Target "+getName(target)+" is a vehicle"); return false; } if(stealth.hasInvisibleBuff(target)) { doLogging("isValidTarget", "Target "+getName(target)+" has a stealth buff"); return false; } // can't attack self if(target == turret) { doLogging("isValidTarget", "I'm attempting to attack myself"); return false; } float range = hasObjVar(turret, RANGE) ? getFloatObjVar(turret, RANGE) : DEFAULT_TURRET_RANGE; // Range check float dist = getDistance(target, turret); if(dist > range + 1) { doLogging("isValidTarget", "Target out of range. dist: " + dist + " range: " + range); return false; } if(utils.hasScriptVar(target, TURRET_FRIEND)) { doLogging("isValidTarget", "Target is a friend."); return false; } if(isDead(target) || isIncapacitated(target)) { doLogging("isValidTarget", "Target is dead or incapped"); return false; } if(hasScript(turret, "systems.gcw.gcw_turret") && hasScript(target, "ai.smuggler_spawn_enemy")) { return false; //2/1/15: Temp hack so turrets focus on enemy rather than BH NPC's in Bestine } if(ai_lib.isAnimal(target) && hasScript(turret, "systems.gcw.gcw_turret")) { return false; //2/1/15: Temp hack so turrets focus on enemy rather than wild creatures. } if(isMob(target) && isIdValid(getMaster(target))) { obj_id cd = callable.getCallableCD(target); if(callable.getControlDeviceType(cd) == callable.CALLABLE_TYPE_FAMILIAR) { return false; } } obj_id master = getObjIdObjVar(turret, "turret_master"); if(isIdValid(master) && exists(master) && !pvpCanAttack(master, target)) { return false; } string turretFaction = hasObjVar(turret, FACTION) ? getStringObjVar(turret, FACTION) : getBaseFaction(turret); if(!turretFaction.equals("renegade")) { obj_id pvpTarget = target; if(isMob(target) && !isPlayer(target) && isIdValid(getMaster(target))) { pvpTarget = getMaster(target); } if(!pvpCanAttack(turret, pvpTarget)) { doLogging("isValidTarget", "Our factions are not opposed: Self/Target: "+factions.getFaction(turret)+"/"+factions.getFaction(target)); return false; } } return true; } string getBaseFaction(obj_id turret) { obj_id parentBase = getObjIdObjVar(turret, "hq.defense.parent"); if (!isIdValid(parentBase)) return "renegade"; return getStringObjVar(parentBase, "faction"); } void addTarget(obj_id turret, obj_id target) { if(!isValidTarget(turret, target)) { doLogging("addTarget", ""+getName(target)+"/"+target+" was invalid"); return; } Vector targets = null; if(!utils.hasScriptVar(turret, TARGETS)) { targets = new Vector(); } else { targets = utils.getResizeableObjIdBatchScriptVar(turret, TARGETS); } if(targets != null && targets.indexOf(target) == -1) { targets.add(target); } if(targets.size() > 0) { utils.setBatchScriptVar(turret, TARGETS, targets); if(!isEngaged(turret)) { attackPulse(turret); } } return; } void addTargets(obj_id turret, obj_id[] newTargets) { Vector targets = null; if(!utils.hasScriptVar(turret, TARGETS)) { targets = new Vector(); } else { targets = utils.getResizeableObjIdBatchScriptVar(turret, TARGETS); } for(int i = 0; i < newTargets.length; i++) { if(!isValidTarget(turret, newTargets[i]) || targets.indexOf(newTargets[i]) != -1) { doLogging("addTargets", ""+getName(newTargets[i])+"/"+newTargets[i]+" was invalid or already in my target array"); continue; } targets.add(newTargets[i]); } if(targets.size() > 0) { utils.setBatchScriptVar(turret, TARGETS, targets); if(!isEngaged(turret)) { messageTo(turret, "handleTurretAttack", null, 1, false); } } return; } void removeTarget(obj_id turret, obj_id target) { if(!isIdValid(turret) || !isIdValid(target)) { doLogging("removeTarget", "Removed "+target); return; } if(!utils.hasScriptVar(turret, TARGETS)) { return; } Vector targets = utils.getResizeableObjIdBatchScriptVar(turret, TARGETS); obj_id engageTarget = utils.getObjIdScriptVar(turret, IS_ENGAGED); int idx = targets.indexOf(target); if(idx > -1) { if(isIdValid(engageTarget) && target == engageTarget) { stopTrackingTarget(turret); } targets.remove(idx); if(targets.size() == 0) { utils.removeBatchScriptVar(turret, TARGETS); } else { utils.setBatchScriptVar(turret, TARGETS, targets); } } return; } boolean handleTurretDamage(obj_id turret, obj_id attacker, obj_id weapon, int dmgAmount) { if (!validateAttacker(turret, attacker)) { doLogging("handleTurretDamage", getName(attacker)+"/"+attacker+" was not a valid attacker."); return false; } int curHP = getHitpoints(turret); if(curHP < 1) { explodeTurret(turret, attacker); return true; } addTarget(turret, attacker); if(!utils.hasScriptVar(turret, "playingEffect")) { int smolder = 2000; int fire = 1000; if(curHP < smolder) { location death = getLocation(turret); utils.setScriptVar(turret, "playingEffect", 1); string effect = ""; if(curHP < fire) { effect = "clienteffect/lair_hvy_damage_fire.cef"; } else { effect = "clienteffect/lair_med_damage_smoke.cef"; } playClientEffectLoc(attacker, effect , death, 0); messageTo(turret, "effectManager", null, 15, true); } } return true; } boolean validateAttacker(obj_id turret, obj_id attacker) { obj_id master = getObjIdObjVar(turret, "turret_master"); if(isIdValid(master) && exists(master) && !pvpCanAttack(master, attacker)) { return false; } return pvpIsEnemy(turret, attacker); } void explodeTurret(obj_id turret, obj_id killer) { location death = getLocation(turret); deactivateTurret(turret); playClientEffectLoc(turret, "clienteffect/combat_explosion_lair_large.cef", death, 0); messageTo(turret, "handleDestroyTurret", null, 2, false); return; } obj_id getGoodTurretTarget(obj_id turret) { // see who the last person was that we shot at and see if they are still good obj_id target = getCurrentTarget(turret); if(!isValidTarget(turret, target) || isIncapacitated(target) || isDead(target) || !canSee(turret, target) ) { if(isIncapacitated(target) && !isDead(target)) { pclib.coupDeGrace(target, turret, false); } target = null; } else { return target; } if(!utils.hasScriptVar(turret, TARGETS)) { return null; } //remove incapacitated targets: Vector targets = utils.getResizeableObjIdBatchScriptVar(turret, TARGETS); if((targets == null) || (targets.size() == 0)) { doLogging("getGoodTurretTarget", "I had no target batchvar so I am returning null"); return null; } target = null; while(!isIdValid(target)) { if(targets.size() < 1) { break; } target = (obj_id)targets.get(rand(0, targets.size() - 1)); if(!isValidTarget(turret, target) || isDead(target) || isIncapacitated(target) || !canSee(turret, target)|| vehicle.isVehicle(target) || stealth.hasInvisibleBuff(target)) { targets.removeElement(target); target = null; continue; } } if(targets.size() < 1) { stopTrackingTarget(turret); utils.removeBatchScriptVar(turret, TARGETS); } else { utils.setBatchScriptVar(turret, TARGETS, targets); } return target; } void attackPulse(obj_id turret) { if(!isActive(turret)) { return; } obj_id target = getGoodTurretTarget(turret); if(!isIdValid(target)) { stopTrackingTarget(turret); return; } startTrackingTarget(turret, target); executeAttackAction(turret, target); } void executeAttackAction(obj_id turret, obj_id target) { obj_id weapon = getTurretWeapon(turret); if(!isIdValid(weapon)) { stopTrackingTarget(turret); } dictionary params = new dictionary(); params.put("target", target); messageTo(turret, "turretShot", params, getWeaponAttackSpeed(weapon), false); return; } void doLogging(string section, string message) { LOG("doLogging/advanced_turret.scriptlib/"+section, message); }