1
/*jslint indent: 4, maxerr: 50, white: true, browser: false, undef: true, nomen: true, plusplus: true, bitwise: true, regexp: true, newcap: true, sloppy: false */
5
** Copyright (C) 2012 Tricky
7
** This work is licensed under the Creative Commons
8
** Attribution-Noncommercial-Share Alike 3.0 Unported License.
10
** To view a copy of this license, visit
11
** http://creativecommons.org/licenses/by-nc-sa/3.0/ or send a letter
12
** to Creative Commons, 171 Second Street, Suite 300, San Francisco,
13
** California, 94105, USA.
15
** Ship related functions for the patrol AI.
16
** Missile subentity loading based on tgGeneric_externalMissiles.js by Thargoid
19
this.name = "jaguar_company_ship.js";
20
this.author = "Tricky";
21
this.copyright = "© 2012 Tricky";
22
this.license = "CC BY-NC-SA 3.0";
23
this.description = "Ship script for the Jaguar Company.";
29
this.shipSpawned = function shipSpawned() {
33
/* OU's, GOU's, LOU's and (d)ROU's. Also some names I really like. */
34
shipNames = ["Profit Margin", "Trade Surplus", "Limiting Factor", "Gunboat Diplomat", "Zealot", "Xenophobe", "God Told Me To Do It", "Just Another Victim Of The Ambient Morality", "Synchronize Your Dogmas", "Thank you And Goodnight", "Well I Was In The Neighbourhood", "You'll Thank Me Later", "Shoot Them Later", "Attitude Adjuster", "Killing Time", "I Blame Your Mother", "I Blame My Mother", "Heavy Messing", "Frank Exchange Of Views", "Nuisance Value", "All Through With This Niceness And Negotiation Stuff", "I Said, I've Got A Big Stick", "Hand Me The Gun And Ask Me Again", "But Who's Counting?", "Germane Riposte", "We Haven't Met But You're A Great Fan Of Mine", "All The Same, I Saw It First", "Ravished By The Sheer Implausibility Of That Last Statement", "Zero Credibility", "Charming But Irrational", "Demented But Determined", "You May Not Be The Coolest Person Here", "Lucid Nonsense", "Awkward Customer", "Conventional Wisdom", "Fine Till You Came Along", "I Blame The Parents", "Inappropriate Response", "A Momentary Lapse Of Sanity", "Lapsed Pacifist", "Reformed Nice Guy", "Pride Comes Before A Fall", "Injury Time", "Now Look What You've Made Me Do", "Kiss This Then", "Eight Rounds Rapid", "You'll Clean That Up Before You Leave", "Me, I'm Counting", "The Usual But Etymologically Unsatisfactory", "Falling Outside The Normal Moral Constraints", "Hylozoist", "No One Knows What The Dead Think", "Flick to Kick", "Your Egg's Broken But Mine Is Ok", "Shall I Be Mummy?", "Is This Galaxy Taken?", "Famous Last Words", "Road Rage", "Live A Little", "Not in My Back Yard", "Playing A Sweeper", "You're Going Home In A Fracking Ambulance", "Rear Entry", "Open Wide, Say Aaaarrgghhh", "Hope You Like Explosions", "I Haven't Seen One Of Those For Years", "Are You Religious?", "Not Now Dear", "Something Had To Be Done", "Hideously Indefensible Sense Of Humour", "Camouflage", "Come And Have A Go If You Think You're Hard Enough", "Throwing Toys Out The Crib", "Podex Perfectus Es", "Stercorem Pro Cerebro Habes", "Futue Te Ipsum Et Caballum Tuum", "Remember To Wash Your Hands", "One Out All Out", "Looking At Me, Pal?", "You Showed Me Yours, Now I'll Show You Mine", "Salt In Your Vaseline", "Cracking My Knuckles", "Break Glass In Case Of War", "My Turn", "No Pun Intended", "Look No Hands", "Very Sharp Stick", "Weapons Of Mass Deception", "...And Another Thing", "Clerical Error", "Silly Mid On", "You And Whose Army?", "This Sector Ain't Big Enough For The Both Of Us", "Diplomacy Was Never My Strong Suite", "Such A Pretty Big Red Button", "Synthetic Paragon Rubber Company", "Forget And Fire", "I Was Just Following Orders", "Weapon of Mass Distraction", "Forgive and Forget", "Innocence Is No Excuse", "Psychosis Is Only One State Of Mind", "Lets Dance", "AI Avenger", "Dead Man Walking", "A Little Less Conversation", "Here One Minute, Gone The Next", "Here, Let Me Escort You", "Killed With Superior Skill", "External Agitation", "Catch Me If You Can", "But What About The Children?", "Single Fingered Hand Gestures", "A World Of Hurt", "Looking Down The Gun Barrel", "Terminal Atomic Headache", "Know Thy Enemy", "Cold Steel For An Iron Age", "The Malevolent Creation", "Gamma Ray Goggles", "End Of Green", "Terrorwheel", "Sickening Sense Of Humour", "Mines Bigger", "Friendly Fire Isn't", "No Need For Stealth", "All Guns Blazin!", "Harmony Dies", "The Controlled Psychopath", "It Ends Now", "Forced To Be Nice", "Axis of Advance", "Acts of God", "The Feeling's Mutual", "The Beautiful Nightmare", "If You Can Read This...", "Are You Saved?", "Cunning Linguist", "Gay Abandon", "My Finger", "Got Legs", "Hose Job", "Protect And Sever", "Rebuttal", "Not In The Face", "I Have Right Of Way", "It Ran Into My Missile", "Have A Nice Rest Of Your Life", "Nose Job", "Get My Point?", "Grid Worker", "Eraserhead", "What Star?", "All This (And Brains)", "Random Acts Of Senseless Violence", "God Will Recognize His Own", "Would You Like A Quick Suppository With That?", "Pop Me A Couple More Of Those Happy Pills (Eccentric)", "Trouble Maker?", "Talk Is Cheap", "Tightly Strung", "Have You Kept The Receipt?", "It Was Broke When I Got Here", "Insanity Plea Rejected", "Thora Hird", "Barbara Cartland", "Freddy Starr Ate My Hamster", "And You Thought You Knew What Terror Means", "I'm A 'Shoot First, Ask Questions Later' Kinda Guy", "Duck You Suckers", "Trumpton Riots", "Dodgy Transformer"];
36
if (worldScripts["Jaguar Company"].logAIMessages) {
37
this.ship.reportAIMessages = true;
40
this.ship.displayName = "Jaguar Company: " + shipNames[Math.floor(Math.random() * shipNames.length)];
41
this.route = "JAGUAR_COMPANY_BASE_TO_WP";
43
/* just to ensure ship is fully loaded with selected missile type and nothing else. */
44
if (this.ship.scriptInfo.missileRole) {
45
/* missileRole should be defined in shipdata.plist */
46
this.missileRole = this.ship.scriptInfo.missileRole;
48
/* default to standard missile if not. */
49
this.missileRole = "EQ_HARDENED_MISSILE";
52
/* Thargoid's missile code. */
53
if (this.ship.scriptInfo.initialMissiles) {
54
this.initialMissiles = parseInt(this.ship.scriptInfo.initialMissiles, 10);
56
this.initialMissiles = this.ship.missileCapacity;
59
if (this.ship.missiles.length > 0) {
60
/* remove all spawning missiles and restock with selected ones. */
61
this.ship.awardEquipment("EQ_MISSILE_REMOVAL");
64
for (addCounter = 0; addCounter < this.initialMissiles; addCounter++) {
65
this.ship.awardEquipment(this.missileRole);
69
/* Thargoid's missile code. */
70
this.shipFiredMissile = function shipFiredMissile(missile, target) {
73
if (this.ship.subEntities.length === 0) {
74
/* if we've run out of sub-ents before we run out of missiles. */
78
/* Set counter to number of sub-ents minus 1 (as entity array goes up from zero). */
79
subCounter = this.ship.subEntities.length - 1;
81
for (subCounter = this.ship.subEntities.length - 1; subCounter >= 0; subCounter--) {
82
if (this.ship.subEntities[subCounter].hasRole(missile.primaryRole)) {
83
/* if the sub-ent is the same as the missile being fired. */
84
/* move the fired missile to the sub-ent position. */
85
missile.position = this.$localToGlobal(this.ship.subEntities[subCounter].position);
86
/* point the missile in the right direction. */
87
missile.orientation = this.ship.subEntities[subCounter].orientation.multiply(this.ship.orientation);
88
missile.desiredSpeed = missile.maxSpeed;
89
/* remove the sub-ent version of the missile. */
90
this.ship.subEntities[subCounter].remove();
92
/* come out of the loop, as we've done our swap. */
98
/* Thargoid's missile code. */
99
this.$localToGlobal = function $localToGlobal(position) {
100
/* sub-ent position is relative to mother, but for swapping we need the absolute global position. */
101
var orientation = this.ship.orientation;
103
return this.ship.position.add(position.rotateBy(orientation));
106
/* Thargoid's missile code. */
107
this.shipTakingDamage = function shipTakingDamage(amount, fromEntity, damageType) {
112
if (this.ship.missiles.length === 0 && this.ship.subEntities.length === 0) {
113
/* if we're all out of missiles and any sub-entities, bail out. */
117
this.missileSubs = 0;
119
/* Initially set subCounter to number of sub-ents minus 1 (as entity array goes up from zero). */
120
for (subCounter = this.ship.subEntities.length - 1; subCounter >= 0; subCounter--) {
121
if (this.ship.subEntities[subCounter].hasRole(this.missileRole)) {
122
/* if the sub-ent is a missile, count it. */
127
if (this.missileSubs === 0 && this.ship.subEntities.length === 0) {
128
/* if we're all out of missiles and missile sub-entities, bail out. */
132
if (this.missileSubs < this.ship.missiles.length) {
133
/* if we've got more missiles than sub-entity missiles. */
134
/* get rid of all missiles. */
135
this.ship.awardEquipment("EQ_MISSILE_REMOVAL");
137
if (this.missileSubs > 0) {
138
for (missileCounter = 0; missileCounter < this.missileSubs; missileCounter++) {
139
/* restock with the correct number of selected missile. */
140
this.ship.awardEquipment(this.missileRole);
147
if (this.missileSubs > this.ship.missiles.length) {
148
/* if we've got less missiles than sub-entity missiles. */
149
this.difference = this.missileSubs - this.ship.missiles.length;
151
for (removeCounter = 0; removeCounter < this.difference; removeCounter++) {
152
/* loop through however many subs we need to remove. */
153
/* Initially set subCounter to number of sub-ents minus 1 (as entity array goes up from zero). */
154
for (subCounter = this.ship.subEntities.length - 1; subCounter >= 0; subCounter--) {
155
if (this.ship.subEntities[subCounter].hasRole(this.missileRole)) {
156
/* if the sub-ent is a missile, remove it. */
157
this.ship.subEntities[subCounter].remove();
168
/* Not doing any exotic routes for now. */
169
/* route: Base->WP, WP->PLANET, PLANET->WP, WP->Base */
170
this.$checkCurrentRoute = function $checkCurrentRoute() {
171
switch (this.route) {
172
case "JAGUAR_COMPANY_BASE_TO_WP":
173
this.ship.reactToAIMessage("GOTO_WITCHPOINT_FROM_BASE");
175
case "JAGUAR_COMPANY_WP_TO_PLANET":
176
this.ship.reactToAIMessage("GOTO_PLANET");
178
case "JAGUAR_COMPANY_PLANET_TO_WP":
179
this.ship.reactToAIMessage("GOTO_WITCHPOINT");
181
case "JAGUAR_COMPANY_WP_TO_BASE":
182
this.ship.reactToAIMessage("GOTO_BASE");
185
this.route = "JAGUAR_COMPANY_BASE_TO_WP";
186
this.ship.reactToAIMessage("INVALID_ROUTE");
190
/* Find out our next route. */
191
this.$findNextRoute = function $findNextRoute() {
192
switch (this.route) {
193
case "JAGUAR_COMPANY_BASE_TO_WP":
194
this.route = "JAGUAR_COMPANY_WP_TO_PLANET";
196
case "JAGUAR_COMPANY_WP_TO_PLANET":
197
this.route = "JAGUAR_COMPANY_PLANET_TO_WP";
199
case "JAGUAR_COMPANY_PLANET_TO_WP":
200
this.route = "JAGUAR_COMPANY_WP_TO_BASE";
202
case "JAGUAR_COMPANY_WP_TO_BASE":
203
this.route = "JAGUAR_COMPANY_BASE_TO_WP";
206
this.route = "JAGUAR_COMPANY_BASE_TO_WP";
209
this.$checkCurrentRoute();
212
/* Return the ship furthest away. */
213
this.$queryMaxShip = function $queryMaxShip() {
214
var ships = system.shipsWithRole("jaguar_company_patrol", this.ship);
216
/* If there is no other ships then return null */
217
if (ships.length === 1) {
221
/* Even though the ship group can be set up, all the ships may not be present. */
222
if (ships.length !== worldScripts["Jaguar Company"].shipGroup.ships.length) {
226
/* Return the last ship in the array which is the furthest away. */
227
return ships[ships.length - 1];
230
/* Find the average distance to all the other ships. */
231
this.$queryAverageDistance = function $queryAverageDistance() {
232
var ships = system.shipsWithRole("jaguar_company_patrol", this.ship),
239
/* If there is no other ships then return null */
240
if (ships.length === 1) {
244
if (worldScripts["Jaguar Company"].logging && worldScripts["Jaguar Company"].logExtra) {
245
logStr = this.ship.displayName + " => ";
248
averageDistance = 0.0;
250
for (i = 0, j = 0; i < ships.length; i++) {
251
/* Don't check our own ship. */
252
if (this.ship !== ships[i]) {
253
distance = this.ship.position.distanceTo(ships[i].position);
255
if (worldScripts["Jaguar Company"].logging && worldScripts["Jaguar Company"].logExtra) {
256
logStr += ships[i].displayName + " = " + distance;
258
if (++j !== ships.length - 1) {
263
/* Add up the distances. */
264
averageDistance += distance;
268
/* Average all the distances. */
269
averageDistance /= (ships.length - 1);
271
if (worldScripts["Jaguar Company"].logging && worldScripts["Jaguar Company"].logExtra) {
272
log(this.name, "Avg: " + averageDistance + " (" + logStr + ")");
275
return averageDistance;
278
/* Find the others ships and set the target to the one furthest away. */
279
this.$locateJaguarCompany = function $locateJaguarCompany() {
280
var ships = system.shipsWithRole("jaguar_company_patrol", this.ship),
283
/* Even though the ship group can be set up, all the ships may not be present. */
284
if (ships.length !== worldScripts["Jaguar Company"].shipGroup.ships.length) {
288
maxShip = this.$queryMaxShip();
290
if (maxShip === null) {
291
this.ship.reactToAIMessage("JAGUAR_COMPANY_NOT_FOUND");
296
this.ship.target = maxShip;
297
this.ship.reactToAIMessage("JAGUAR_COMPANY_FOUND");
300
this.$checkJaguarCompanyDistance = function $checkJaguarCompanyDistance() {
301
var ships = system.shipsWithRole("jaguar_company_patrol", this.ship),
304
/* Even though the ship group can be set up, all the ships may not be present. */
305
if (ships.length !== worldScripts["Jaguar Company"].shipGroup.ships.length) {
309
/* Find the average distance to all the other ships. */
310
distance = this.$queryAverageDistance();
312
/* A 'null' distance means no other ships. */
313
if (distance === null) {
317
/* I would love to create a fuzzy logic controller for this. */
318
if (distance < 7500.0) {
319
/* If the average distance is less than 7.5km then we have regrouped. */
320
this.ship.reactToAIMessage("JAGUAR_COMPANY_REGROUPED");
321
} else if (distance >= 7500.0 && distance < 15000.0) {
322
/* If the average distance is between 7.5km and 15km then we are close. */
323
this.ship.reactToAIMessage("JAGUAR_COMPANY_CLOSE");
324
} else if (distance >= 15000.0 && distance < 22500.0) {
325
/* If the average distance is between 15km and 22.5km then we are nearby. */
326
this.ship.reactToAIMessage("JAGUAR_COMPANY_NEARBY");
328
/* If the average distance is more than 22.5km then we are far away. */
329
this.ship.reactToAIMessage("JAGUAR_COMPANY_FAR_AWAY");
333
/* Tell everyone to regroup if the average distance to all the other ships is too great. */
334
this.$checkJaguarCompanyRegroup = function $checkJaguarCompanyRegroup(maxDistance) {
335
var ships = system.shipsWithRole("jaguar_company_patrol", this.ship),
339
/* Even though the ship group can be set up, all the ships may not be present. */
340
if (ships.length !== worldScripts["Jaguar Company"].shipGroup.ships.length) {
344
/* Find the average distance to all the other ships. */
345
distance = this.$queryAverageDistance();
347
/* A 'null' distance means no other ships. */
348
if (distance === null) {
352
if (distance >= maxDistance) {
353
for (i = 0; i < ships.length; i++) {
354
/* Tell all ships, including ourself, to regroup. */
355
ships[i].reactToAIMessage("JAGUAR_COMPANY_REGROUP");
360
/* This does something similar to the groupAttack AI command. */
361
this.$jaguarCompanyAttackTarget = function $jaguarCompanyAttackTarget() {
362
var ships = system.shipsWithRole("jaguar_company_patrol", this.ship),
365
/* Even though the ship group can be set up, all the ships may not be present. */
366
if (ships.length !== worldScripts["Jaguar Company"].shipGroup.ships.length) {
370
for (i = 0; i < ships.length; i++) {
371
/* Other ships may be busy killing baddies, react 25% of the time. */
372
if (this.ship !== ships[i] && Math.random() < 0.25) {
373
log(this.name, "ship #" + i + " responding: " + ships[i].displayName);
374
ships[i].target = this.ship.target;
375
ships[i].reactToAIMessage("JAGUAR_COMPANY_ATTACK_TARGET");
379
/* Respond to our own attack call. */
380
this.ship.reactToAIMessage("JAGUAR_COMPANY_ATTACK_TARGET");
383
/* Locate the base. */
384
this.$locateJaguarCompanyBase = function $locateJaguarCompanyBase() {
385
var ships = system.shipsWithRole("jaguar_company_base", this.ship);
387
if (ships.length === 0) {
388
/* If it has gone, just patrol the witchpoint to the planet lane. */
389
this.route = "JAGUAR_COMPANY_WP_TO_PLANET";
390
this.ship.reactToAIMessage("JAGUAR_COMPANY_BASE_NOT_FOUND");
392
/* Set the target to the base. */
393
this.ship.target = ships[0];
394
this.ship.reactToAIMessage("JAGUAR_COMPANY_BASE_FOUND");
398
/* Won't be needed when v1.78 comes out. */
399
this.$detectQBomb = function $detectQBomb() {
400
var qbomb = system.shipsWithRole("EQ_QC_MINE EQ_CASCADE_MISSILE", this.ship, 25600.0);
402
/* Let v1.77+ handle it. */
403
if (0 >= oolite.compareVersion("1.77")) {
407
if (qbomb.length !== 0) {
408
/* First one is the closest. */
409
this.ship.target = qbomb[0];
410
this.ship.reactToAIMessage("CASCADE_WEAPON_DETECTED");