First go at pirateFreighter/Fighter AIs
Fix bug with launching ships with escorts, tidy up functions New: ship.notifyGroupOfWormhole() Introduce AI template blocks to compact AI writing even further.
This commit is contained in:
parent
bcc93120d7
commit
b00b1bb550
@ -33,7 +33,7 @@ this.aiStarted = function() {
|
||||
this.ai = new worldScripts["oolite-libPriorityAI"].AILib(this.ship);
|
||||
|
||||
ai.setParameter("oolite_flag_watchForCargo",true);
|
||||
/* This communication is necessary but needs more variety and moving to descriptions.plist so it can be translated */
|
||||
|
||||
ai.setCommunicationsRole("pirate");
|
||||
|
||||
|
||||
@ -107,45 +107,7 @@ this.aiStarted = function() {
|
||||
truebranch: [
|
||||
{
|
||||
condition: ai.conditionIsGroupLeader,
|
||||
truebranch: [
|
||||
{
|
||||
condition: ai.conditionFriendlyStationNearby,
|
||||
configuration: ai.configurationSetNearbyFriendlyStationForDocking,
|
||||
behaviour: ai.behaviourDockWithStation,
|
||||
reconsider: 30
|
||||
},
|
||||
{
|
||||
condition: ai.conditionFriendlyStationExists,
|
||||
configuration: ai.configurationSetDestinationToNearestFriendlyStation,
|
||||
behaviour: ai.behaviourApproachDestination,
|
||||
reconsider: 30
|
||||
},
|
||||
{
|
||||
condition: ai.conditionHasSelectedPlanet,
|
||||
truebranch: [
|
||||
{
|
||||
preconfiguration: ai.configurationSetDestinationToSelectedPlanet,
|
||||
condition: ai.conditionNearDestination,
|
||||
behaviour: ai.behaviourLandOnPlanet
|
||||
},
|
||||
{
|
||||
behaviour: ai.behaviourApproachDestination,
|
||||
reconsider: 30
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
condition: ai.conditionPlanetExists,
|
||||
configuration: ai.configurationSelectPlanet,
|
||||
behaviour: ai.behaviourReconsider
|
||||
},
|
||||
{
|
||||
condition: ai.conditionCanWitchspaceOut,
|
||||
configuration: ai.configurationSelectWitchspaceDestination,
|
||||
behaviour: ai.behaviourEnterWitchspace,
|
||||
reconsider: 20
|
||||
}
|
||||
]
|
||||
truebranch: ai.templateReturnToBaseOrPlanet()
|
||||
},
|
||||
/* Once the group leader has docked or landed, another one gets
|
||||
* appointed, and they can decide what to do next */
|
||||
@ -159,28 +121,7 @@ this.aiStarted = function() {
|
||||
falsebranch: [
|
||||
{
|
||||
condition: ai.conditionIsGroupLeader,
|
||||
truebranch: [
|
||||
{
|
||||
preconfiguration: ai.configurationForgetCargoDemand,
|
||||
condition: ai.conditionScannerContainsPirateVictims,
|
||||
configuration: ai.configurationAcquireScannedTarget,
|
||||
truebranch: [
|
||||
{
|
||||
label: "Check odds",
|
||||
condition: ai.conditionCombatOddsGood,
|
||||
behaviour: ai.behaviourRobTarget,
|
||||
reconsider: 5
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
/* move to a position on one of the space lanes, preferring lane 1 */
|
||||
label: "Lurk",
|
||||
configuration: ai.configurationSetDestinationToPirateLurk,
|
||||
behaviour: ai.behaviourApproachDestination,
|
||||
reconsider: 30
|
||||
},
|
||||
]
|
||||
truebranch: ai.templateLeadPirateMission()
|
||||
},
|
||||
{
|
||||
behaviour: ai.behaviourFollowGroupLeader,
|
||||
|
134
Resources/AIs/pirateFighterAI.js
Normal file
134
Resources/AIs/pirateFighterAI.js
Normal file
@ -0,0 +1,134 @@
|
||||
/*
|
||||
|
||||
pirateFighterAI.js
|
||||
|
||||
Priority-based AI for pirate fighters (guards and extra capacity for
|
||||
organised pirate gangs)
|
||||
|
||||
Oolite
|
||||
Copyright © 2004-2013 Giles C Williams and contributors
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
MA 02110-1301, USA.
|
||||
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
this.name = "Oolite Pirate Fighter AI";
|
||||
this.version = "1.79";
|
||||
|
||||
this.aiStarted = function() {
|
||||
this.ai = new worldScripts["oolite-libPriorityAI"].AILib(this.ship);
|
||||
|
||||
ai.setParameter("oolite_flag_watchForCargo",true);
|
||||
|
||||
ai.setCommunicationsRole("pirate");
|
||||
|
||||
|
||||
ai.setPriorities([
|
||||
/* Combat */
|
||||
{
|
||||
condition: ai.conditionLosingCombat,
|
||||
behaviour: ai.behaviourFleeCombat,
|
||||
reconsider: 5
|
||||
},
|
||||
{
|
||||
label: "Cargo demands met?",
|
||||
condition: ai.conditionCargoDemandsMet,
|
||||
/* Let them go if they've dropped enough cargo and stop firing back */
|
||||
truebranch: [
|
||||
{
|
||||
condition: ai.conditionInCombatWithHostiles,
|
||||
configuration: ai.configurationAcquireHostileCombatTarget,
|
||||
behaviour: ai.behaviourRepelCurrentTarget,
|
||||
reconsider: 5
|
||||
}
|
||||
],
|
||||
falsebranch: [
|
||||
{
|
||||
condition: ai.conditionInCombat,
|
||||
configuration: ai.configurationAcquireCombatTarget,
|
||||
behaviour: ai.behaviourDestroyCurrentTarget,
|
||||
reconsider: 5
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
condition: ai.conditionWitchspaceEntryRequested,
|
||||
behaviour: ai.behaviourEnterWitchspace,
|
||||
reconsider: 15
|
||||
},
|
||||
{
|
||||
preconfiguration: ai.configurationCheckScanner,
|
||||
condition: ai.conditionScannerContainsSalvageForGroup,
|
||||
truebranch: [
|
||||
{
|
||||
condition: ai.conditionScannerContainsSalvageForMe,
|
||||
configuration: ai.configurationAcquireScannedTarget,
|
||||
behaviour: ai.behaviourCollectSalvage,
|
||||
reconsider: 20
|
||||
},
|
||||
// if can't scoop, hang around waiting for the others,
|
||||
// unless the entire group has enough cargo
|
||||
{
|
||||
notcondition: ai.conditionGroupHasEnoughLoot,
|
||||
configuration: ai.configurationSetDestinationToGroupLeader,
|
||||
behaviour: ai.behaviourApproachDestination,
|
||||
reconsider: 15
|
||||
}
|
||||
]
|
||||
},
|
||||
/* Stay out of the way of hunters */
|
||||
{
|
||||
condition: ai.conditionScannerContainsHunters,
|
||||
configuration: ai.configurationAcquireScannedTarget,
|
||||
truebranch: [
|
||||
{
|
||||
condition: ai.conditionCombatOddsExcellent,
|
||||
behaviour: ai.behaviourDestroyCurrentTarget,
|
||||
reconsider: 10
|
||||
},
|
||||
{
|
||||
behaviour: ai.behaviourLeaveVicinityOfTarget,
|
||||
reconsider: 20
|
||||
}
|
||||
]
|
||||
},
|
||||
/* Follow leader */
|
||||
{
|
||||
condition: ai.conditionHasMothership,
|
||||
configuration: ai.configurationSetDestinationToGroupLeader,
|
||||
behaviour: ai.behaviourApproachDestination,
|
||||
reconsider: 15
|
||||
},
|
||||
/* Find a new leader, or return to base */
|
||||
{
|
||||
condition: ai.conditionScannerContainsPirateLeader,
|
||||
configuration: ai.configurationAcquireScannedTarget,
|
||||
behaviour: ai.behaviourJoinTargetGroup,
|
||||
reconsider: 10,
|
||||
falsebranch: ai.templateReturnToBaseOrPlanet()
|
||||
},
|
||||
{
|
||||
// full of loot, but stuck in system and no friendly stations
|
||||
configuration: ai.configurationSetDestinationToWitchpoint,
|
||||
// TODO: behaviour search for wormholes
|
||||
behaviour: ai.behaviourApproachDestination,
|
||||
reconsider: 30
|
||||
}
|
||||
]);
|
||||
|
||||
}
|
155
Resources/AIs/pirateFreighterAI.js
Normal file
155
Resources/AIs/pirateFreighterAI.js
Normal file
@ -0,0 +1,155 @@
|
||||
/*
|
||||
|
||||
pirateFreighterAI.js
|
||||
|
||||
Priority-based AI for pirate freighters leading large pirate groups
|
||||
|
||||
Oolite
|
||||
Copyright © 2004-2013 Giles C Williams and contributors
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
MA 02110-1301, USA.
|
||||
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
this.name = "Oolite Pirate Freighter AI";
|
||||
this.version = "1.79";
|
||||
|
||||
this.aiStarted = function() {
|
||||
var ai = new worldScripts["oolite-libPriorityAI"].AILib(this.ship);
|
||||
|
||||
ai.setParameter("oolite_flag_watchForCargo",true);
|
||||
|
||||
ai.setCommunicationsRole("pirate");
|
||||
|
||||
// combat and looting behaviour same at all stages
|
||||
var common = [
|
||||
/* Combat */
|
||||
{
|
||||
condition: ai.conditionLosingCombat,
|
||||
behaviour: ai.behaviourFleeCombat,
|
||||
reconsider: 5
|
||||
},
|
||||
{
|
||||
label: "Cargo demands met?",
|
||||
condition: ai.conditionCargoDemandsMet,
|
||||
/* Let them go if they've dropped enough cargo and stop firing back */
|
||||
truebranch: [
|
||||
{
|
||||
condition: ai.conditionInCombatWithHostiles,
|
||||
configuration: ai.configurationAcquireHostileCombatTarget,
|
||||
behaviour: ai.behaviourRepelCurrentTarget,
|
||||
reconsider: 5
|
||||
}
|
||||
],
|
||||
falsebranch: [
|
||||
{
|
||||
condition: ai.conditionInCombat,
|
||||
configuration: ai.configurationAcquireCombatTarget,
|
||||
behaviour: ai.behaviourDestroyCurrentTarget,
|
||||
reconsider: 5
|
||||
}
|
||||
]
|
||||
},
|
||||
/* Collect loot. Don't worry too much about other group
|
||||
* members. If they pick up stuff too that's good, but this is
|
||||
* the ship with the hold... */
|
||||
{
|
||||
preconfiguration: ai.configurationCheckScanner,
|
||||
condition: ai.conditionScannerContainsSalvageForMe,
|
||||
configuration: ai.configurationAcquireScannedTarget,
|
||||
behaviour: ai.behaviourCollectSalvage,
|
||||
reconsider: 20
|
||||
},
|
||||
/* Are there hunters about? Avoid, or destroy if safe to do so. */
|
||||
{
|
||||
condition: ai.conditionScannerContainsHunters,
|
||||
configuration: ai.configurationAcquireScannedTarget,
|
||||
truebranch: [
|
||||
{
|
||||
condition: ai.conditionCombatOddsExcellent,
|
||||
behaviour: ai.behaviourDestroyCurrentTarget,
|
||||
reconsider: 10
|
||||
},
|
||||
{
|
||||
behaviour: ai.behaviourLeaveVicinityOfTarget,
|
||||
reconsider: 20
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
var specific;
|
||||
if (this.ship.homeSystem == this.ship.destinationSystem)
|
||||
{
|
||||
// local piracy
|
||||
specific = [
|
||||
{
|
||||
label: "Enough loot?",
|
||||
condition: ai.conditionGroupHasEnoughLoot,
|
||||
truebranch: ai.templateReturnToBaseOrPlanet(),
|
||||
falsebranch: ai.templateLeadPirateMission()
|
||||
}
|
||||
];
|
||||
}
|
||||
else if (this.ship.homeSystem == system.ID && this.ship.fuel == 7)
|
||||
{
|
||||
// jump to destination system, taking group
|
||||
specific = ai.templateWitchspaceJumpOutbound().concat(ai.templateReturnToBaseOrPlanet);
|
||||
}
|
||||
else if (this.ship.homeSystem == system.ID)
|
||||
{
|
||||
// if not at full fuel, we're probably returning home. Or
|
||||
// something went wrong when trying to enter witchspace that
|
||||
// needed injectors to fix.
|
||||
specific = ai.templateReturnToBaseOrPlanet();
|
||||
}
|
||||
else
|
||||
{
|
||||
// pirate work, until enough cargo gathered or lose too many
|
||||
// fighters, then jump home if we're in witchspace, just jump
|
||||
// home immediately!
|
||||
specific = [
|
||||
{
|
||||
condition: ai.conditionGroupHasEnoughLoot,
|
||||
truebranch: ai.templateWitchspaceJumpOutbound()
|
||||
},
|
||||
{
|
||||
condition: ai.conditionGroupAttritionReached,
|
||||
truebranch: ai.templateWitchspaceJumpOutbound()
|
||||
},
|
||||
{
|
||||
condition: ai.conditionInInterstellarSpace,
|
||||
truebranch: ai.templateWitchspaceJumpOutbound(),
|
||||
falsebranch: ai.templateLeadPirateMission()
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
var fallback = [
|
||||
{
|
||||
// stuck in system and no friendly stations
|
||||
configuration: ai.configurationSetDestinationToWitchpoint,
|
||||
// TODO: behaviour search for wormholes
|
||||
behaviour: ai.behaviourApproachDestination,
|
||||
reconsider: 30
|
||||
}
|
||||
];
|
||||
|
||||
var priorities = common.concat(specific).concat(fallback);
|
||||
ai.setPriorities(priorities);
|
||||
}
|
@ -73,63 +73,9 @@ this.aiStarted = function() {
|
||||
{
|
||||
condition: ai.conditionCargoIsProfitableHere,
|
||||
// branch to head for station
|
||||
truebranch: [
|
||||
{
|
||||
condition: ai.conditionHasSelectedStation,
|
||||
truebranch: [
|
||||
{
|
||||
condition: ai.conditionSelectedStationNearby,
|
||||
configuration: ai.configurationSetSelectedStationForDocking,
|
||||
behaviour: ai.behaviourDockWithStation,
|
||||
reconsider: 30
|
||||
},
|
||||
{
|
||||
condition: ai.conditionSelectedStationNearMainPlanet,
|
||||
truebranch: [
|
||||
{
|
||||
notcondition: ai.conditionMainPlanetNearby,
|
||||
configuration: ai.configurationSetDestinationToMainPlanet,
|
||||
behaviour: ai.behaviourApproachDestination,
|
||||
reconsider: 30
|
||||
}
|
||||
]
|
||||
},
|
||||
// either the station isn't near the planet, or we are
|
||||
{
|
||||
configuration: ai.configurationSetDestinationToSelectedStation,
|
||||
behaviour: ai.behaviourApproachDestination,
|
||||
reconsider: 30
|
||||
}
|
||||
],
|
||||
falsebranch: [
|
||||
{
|
||||
configuration: ai.configurationSelectRandomTradeStation,
|
||||
behaviour: ai.behaviourReconsider
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
truebranch: ai.templateReturnToBase(),
|
||||
// jump to another system if possible, sunskim if not
|
||||
falsebranch: [
|
||||
{
|
||||
preconfiguration: ai.configurationSelectWitchspaceDestinationOutbound,
|
||||
condition: ai.conditionCanWitchspaceOnRoute,
|
||||
behaviour: ai.behaviourEnterWitchspace,
|
||||
reconsider: 20
|
||||
},
|
||||
{
|
||||
condition: ai.conditionReadyToSunskim,
|
||||
configuration: ai.configurationSetDestinationToSunskimEnd,
|
||||
behaviour: ai.behaviourSunskim,
|
||||
reconsider: 20
|
||||
},
|
||||
{
|
||||
condition: ai.conditionSunskimPossible,
|
||||
configuration: ai.configurationSetDestinationToSunskimStart,
|
||||
behaviour: ai.behaviourApproachDestination,
|
||||
reconsider: 30
|
||||
}
|
||||
]
|
||||
falsebranch: ai.templateWitchspaceJumpOutbound()
|
||||
}, // end of cargoprofitable true/false branches
|
||||
{
|
||||
// if we're here, the cargo isn't profitable, and we can't
|
||||
|
@ -65,7 +65,6 @@ this.AILib = function(ship)
|
||||
|
||||
/* Cache variables used by utility functions */
|
||||
var condmet = true;
|
||||
var logging = false;
|
||||
|
||||
/* Private utility functions. Cannot be called from external code */
|
||||
|
||||
@ -89,7 +88,7 @@ this.AILib = function(ship)
|
||||
|
||||
/* Considers a priority list, potentially recursively */
|
||||
function _reconsiderList(priorities) {
|
||||
logging = this.getParameter("oolite_flag_behaviourLogging")
|
||||
var logging = this.getParameter("oolite_flag_behaviourLogging");
|
||||
var pl = priorities.length;
|
||||
if (logging)
|
||||
{
|
||||
@ -311,6 +310,20 @@ this.AILib = function(ship)
|
||||
}
|
||||
|
||||
|
||||
this.clearHandlers = function()
|
||||
{
|
||||
// delete all handlers to allow rebinding
|
||||
activeHandlers = Object.keys(handlerCache);
|
||||
for (var i=0; i < activeHandlers.length ; i++)
|
||||
{
|
||||
delete this.ship.AIScript[activeHandlers[i]];
|
||||
}
|
||||
handlerCache = {};
|
||||
delete this.ship.AIScript.aiAwoken;
|
||||
delete this.ship.AIScript.shipDied;
|
||||
}
|
||||
|
||||
|
||||
this.communicate = function(key,params,priority)
|
||||
{
|
||||
if (priority > 1)
|
||||
@ -413,6 +426,7 @@ this.AILib = function(ship)
|
||||
this.setPriorities = function(priorities)
|
||||
{
|
||||
priorityList = priorities;
|
||||
this.clearHandlers();
|
||||
this.applyHandlers({});
|
||||
_resetReconsideration.call(this,Math.random());
|
||||
}
|
||||
@ -928,6 +942,29 @@ AILib.prototype.conditionCombatOddsExcellent = function()
|
||||
}
|
||||
|
||||
|
||||
// group has taken too many losses
|
||||
AILib.prototype.conditionGroupAttritionReached = function()
|
||||
{
|
||||
var group;
|
||||
if (!(group = this.ship.group))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
var cgp = this.getParameter("oolite_groupPower");
|
||||
if (!cgp)
|
||||
{
|
||||
this.setParameter("oolite_groupPower",group.count);
|
||||
return false;
|
||||
}
|
||||
if (group.count > cgp)
|
||||
{
|
||||
this.setParameter("oolite_groupPower",group.count);
|
||||
return false;
|
||||
}
|
||||
return group.count < cgp*0.75;
|
||||
}
|
||||
|
||||
|
||||
AILib.prototype.conditionInCombat = function()
|
||||
{
|
||||
if (this.__cache.conditionInCombat !== undefined)
|
||||
@ -1615,6 +1652,14 @@ AILib.prototype.conditionScannerContainsNonThargoid = function()
|
||||
}
|
||||
|
||||
|
||||
AILib.prototype.conditionScannerContainsPirateLeader = function()
|
||||
{
|
||||
return this.checkScannerWithPredicate(function(s) {
|
||||
return s.group && s.group.leader == s && (s.primaryRole.match(/pirate-.*-freighter/));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
AILib.prototype.conditionScannerContainsPirateVictims = function()
|
||||
{
|
||||
return this.checkScannerWithPredicate(function(s) {
|
||||
@ -1776,23 +1821,27 @@ AILib.prototype.conditionCanScoopCargo = function()
|
||||
|
||||
AILib.prototype.conditionCargoIsProfitableHere = function()
|
||||
{
|
||||
// cargo is always considered profitable in the designated
|
||||
// destination system (assume they have a prepared buyer)
|
||||
if (this.ship.destinationSystem && this.ship.destinationSystem == system.ID)
|
||||
// only consider these values if the ship has a route defined
|
||||
if (this.ship.homeSystem != this.ship.destinationSystem)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
// cargo is never considered profitable in the designated source
|
||||
// system (or you could get ships launching and immediately
|
||||
// redocking)
|
||||
if (this.ship.homeSystem && this.ship.homeSystem == system.ID)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// and allow ships to be given multi-system trips if wanted
|
||||
if (this.getParameter("oolite_flag_noDockingUntilDestination"))
|
||||
{
|
||||
return false;
|
||||
// cargo is always considered profitable in the designated
|
||||
// destination system (assume they have a prepared buyer)
|
||||
if (this.ship.destinationSystem && this.ship.destinationSystem == system.ID)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
// cargo is never considered profitable in the designated source
|
||||
// system (or you could get ships launching and immediately
|
||||
// redocking)
|
||||
if (this.ship.homeSystem && this.ship.homeSystem == system.ID)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// and allow ships to be given multi-system trips if wanted
|
||||
if (this.getParameter("oolite_flag_noDockingUntilDestination"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!system.mainStation)
|
||||
@ -2192,6 +2241,12 @@ AILib.prototype.behaviourEnterWitchspace = function()
|
||||
{
|
||||
// the wormhole we were trying for has expired
|
||||
this.setParameter("oolite_witchspaceWormhole",null);
|
||||
if (this.ship.group && this.ship.group.leader && this.ship.group.leader.status == "STATUS_ENTERING_WITCHSPACE")
|
||||
{
|
||||
// left behind, so leave group
|
||||
this.ship.group.removeShip(this.ship);
|
||||
this.ship.group = null;
|
||||
}
|
||||
}
|
||||
else if (wormhole)
|
||||
{
|
||||
@ -2241,6 +2296,7 @@ AILib.prototype.behaviourEnterWitchspace = function()
|
||||
// if it doesn't, we'll get blocked
|
||||
if (result)
|
||||
{
|
||||
this.ship.notifyGroupOfWormhole();
|
||||
this.setParameter("oolite_witchspaceEntry",null);
|
||||
}
|
||||
}
|
||||
@ -2404,6 +2460,17 @@ AILib.prototype.behaviourGuardTarget = function()
|
||||
}
|
||||
|
||||
|
||||
AILib.prototype.behaviourJoinTargetGroup = function()
|
||||
{
|
||||
if (this.ship.target && this.ship.target.group)
|
||||
{
|
||||
this.ship.target.group.addShip(this.ship);
|
||||
this.ship.group = this.ship.target.group;
|
||||
}
|
||||
this.ship.performIdle();
|
||||
}
|
||||
|
||||
|
||||
AILib.prototype.behaviourLandOnPlanet = function()
|
||||
{
|
||||
this.ship.desiredSpeed = this.ship.maxSpeed / 4;
|
||||
@ -3235,23 +3302,36 @@ AILib.prototype.configurationSelectWitchspaceDestination = function()
|
||||
return;
|
||||
}
|
||||
var possible = system.info.systemsInRange(this.ship.fuel);
|
||||
var selected = possible[Math.floor(Math.random()*possible.length)];
|
||||
this.setParameter("oolite_witchspaceDestination",selected.systemID);
|
||||
this.communicate("oolite_selectedWitchspaceDestination",{"oolite_witchspaceDestination":selected.name},4);
|
||||
if (possible.length > 0)
|
||||
{
|
||||
var selected = possible[Math.floor(Math.random()*possible.length)];
|
||||
this.setParameter("oolite_witchspaceDestination",selected.systemID);
|
||||
this.communicate("oolite_selectedWitchspaceDestination",{"oolite_witchspaceDestination":selected.name},4);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.setParameter("oolite_witchspaceDestination",null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
AILib.prototype.configurationSelectWitchspaceDestinationInbound = function()
|
||||
{
|
||||
var dest = this.ship.homeSystem;
|
||||
this.setWitchspaceRouteTo(dest);
|
||||
if (this.ship.homeSystem == this.ship.destinationSystem)
|
||||
{
|
||||
return this.configurationSelectWitchspaceDestination();
|
||||
}
|
||||
this.setWitchspaceRouteTo(this.ship.homeSystem);
|
||||
}
|
||||
|
||||
|
||||
AILib.prototype.configurationSelectWitchspaceDestinationOutbound = function()
|
||||
{
|
||||
var dest = this.ship.destinationSystem;
|
||||
this.setWitchspaceRouteTo(dest);
|
||||
if (this.ship.homeSystem == this.ship.destinationSystem)
|
||||
{
|
||||
return this.configurationSelectWitchspaceDestination();
|
||||
}
|
||||
this.setWitchspaceRouteTo(this.ship.destinationSystem);
|
||||
}
|
||||
|
||||
|
||||
@ -4526,10 +4606,175 @@ AILib.prototype.responseComponent_trackPlayer_playerWillEnterWitchspace = functi
|
||||
|
||||
|
||||
|
||||
/* ******************* Templates *************************** */
|
||||
|
||||
/* Templates. Common AI priority list fragments which may be useful to
|
||||
* multiple AIs. These functions take no parameters and return a
|
||||
* list. This can either be used straightforwardly as a truebranch or
|
||||
* falsebranch value, or appended to a list using Array.concat() */
|
||||
|
||||
AILib.prototype.templateLeadPirateMission = function()
|
||||
{
|
||||
return [
|
||||
{
|
||||
preconfiguration: this.configurationForgetCargoDemand,
|
||||
condition: this.conditionScannerContainsPirateVictims,
|
||||
configuration: this.configurationAcquireScannedTarget,
|
||||
truebranch: [
|
||||
{
|
||||
label: "Check odds",
|
||||
condition: this.conditionCombatOddsGood,
|
||||
behaviour: this.behaviourRobTarget,
|
||||
reconsider: 5
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
/* move to a position on one of the space lanes, preferring lane 1 */
|
||||
label: "Lurk",
|
||||
configuration: this.configurationSetDestinationToPirateLurk,
|
||||
behaviour: this.behaviourApproachDestination,
|
||||
reconsider: 30
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
AILib.prototype.templateReturnToBase = function()
|
||||
{
|
||||
return [
|
||||
{
|
||||
condition: this.conditionHasSelectedStation,
|
||||
truebranch: [
|
||||
{
|
||||
condition: this.conditionSelectedStationNearby,
|
||||
configuration: this.configurationSetSelectedStationForDocking,
|
||||
behaviour: this.behaviourDockWithStation,
|
||||
reconsider: 30
|
||||
},
|
||||
{
|
||||
condition: this.conditionSelectedStationNearMainPlanet,
|
||||
truebranch: [
|
||||
{
|
||||
notcondition: this.conditionMainPlanetNearby,
|
||||
configuration: this.configurationSetDestinationToMainPlanet,
|
||||
behaviour: this.behaviourApproachDestination,
|
||||
reconsider: 30
|
||||
}
|
||||
]
|
||||
},
|
||||
// either the station isn't near the planet, or we are
|
||||
{
|
||||
configuration: this.configurationSetDestinationToSelectedStation,
|
||||
behaviour: this.behaviourApproachDestination,
|
||||
reconsider: 30
|
||||
}
|
||||
],
|
||||
falsebranch: [
|
||||
{
|
||||
configuration: this.configurationSelectRandomTradeStation,
|
||||
behaviour: this.behaviourReconsider
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
AILib.prototype.templateReturnToBaseOrPlanet = function()
|
||||
{
|
||||
return [
|
||||
{
|
||||
condition: this.conditionFriendlyStationNearby,
|
||||
configuration: this.configurationSetNearbyFriendlyStationForDocking,
|
||||
behaviour: this.behaviourDockWithStation,
|
||||
reconsider: 30
|
||||
},
|
||||
{
|
||||
condition: this.conditionFriendlyStationExists,
|
||||
configuration: this.configurationSetDestinationToNearestFriendlyStation,
|
||||
behaviour: this.behaviourApproachDestination,
|
||||
reconsider: 30
|
||||
},
|
||||
{
|
||||
condition: this.conditionHasSelectedPlanet,
|
||||
truebranch: [
|
||||
{
|
||||
preconfiguration: this.configurationSetDestinationToSelectedPlanet,
|
||||
condition: this.conditionNearDestination,
|
||||
behaviour: this.behaviourLandOnPlanet
|
||||
},
|
||||
{
|
||||
behaviour: this.behaviourApproachDestination,
|
||||
reconsider: 30
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
condition: this.conditionPlanetExists,
|
||||
configuration: this.configurationSelectPlanet,
|
||||
behaviour: this.behaviourReconsider
|
||||
},
|
||||
{
|
||||
condition: this.conditionCanWitchspaceOut,
|
||||
configuration: this.configurationSelectWitchspaceDestination,
|
||||
behaviour: this.behaviourEnterWitchspace,
|
||||
reconsider: 20
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
AILib.prototype.templateWitchspaceJumpInbound = function()
|
||||
{
|
||||
return [
|
||||
{
|
||||
preconfiguration: this.configurationSelectWitchspaceDestinationInbound,
|
||||
condition: this.conditionCanWitchspaceOnRoute,
|
||||
behaviour: this.behaviourEnterWitchspace,
|
||||
reconsider: 20
|
||||
},
|
||||
{
|
||||
condition: this.conditionReadyToSunskim,
|
||||
configuration: this.configurationSetDestinationToSunskimEnd,
|
||||
behaviour: this.behaviourSunskim,
|
||||
reconsider: 20
|
||||
},
|
||||
{
|
||||
condition: this.conditionSunskimPossible,
|
||||
configuration: this.configurationSetDestinationToSunskimStart,
|
||||
behaviour: this.behaviourApproachDestination,
|
||||
reconsider: 30
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
AILib.prototype.templateWitchspaceJumpOutbound = function()
|
||||
{
|
||||
return [
|
||||
{
|
||||
preconfiguration: this.configurationSelectWitchspaceDestinationOutbound,
|
||||
condition: this.conditionCanWitchspaceOnRoute,
|
||||
behaviour: this.behaviourEnterWitchspace,
|
||||
reconsider: 20
|
||||
},
|
||||
{
|
||||
condition: this.conditionReadyToSunskim,
|
||||
configuration: this.configurationSetDestinationToSunskimEnd,
|
||||
behaviour: this.behaviourSunskim,
|
||||
reconsider: 20
|
||||
},
|
||||
{
|
||||
condition: this.conditionSunskimPossible,
|
||||
configuration: this.configurationSetDestinationToSunskimStart,
|
||||
behaviour: this.behaviourApproachDestination,
|
||||
reconsider: 30
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/* ******************* Waypoint generators *********************** */
|
||||
|
||||
@ -4537,7 +4782,7 @@ AILib.prototype.responseComponent_trackPlayer_playerWillEnterWitchspace = functi
|
||||
* the next waypoint for the ship. Ideally ships should either
|
||||
* reach that waypoint or formally give up on it before asking for
|
||||
* the next one, but the generator shouldn't assume that unless
|
||||
* it's one written specifically for a particular AI. */
|
||||
* it's one written specifically for a particular AI . */
|
||||
|
||||
AILib.prototype.waypointsSpacelanePatrol = function()
|
||||
{
|
||||
|
@ -1123,14 +1123,14 @@ this._addPirateAssistant = function(role,lead)
|
||||
asst[0].setBounty(20+system.government+Math.floor(Math.random()*12),"setup actions");
|
||||
asst[0].group = lead.group;
|
||||
lead.group.addShip(asst[0]);
|
||||
if (role == "pirate-interceptor")
|
||||
/* if (role == "pirate-interceptor")
|
||||
{
|
||||
// asst[0].switchAI("pirateInterceptorAI.js");
|
||||
asst[0].switchAI("pirateInterceptorAI.js");
|
||||
}
|
||||
else
|
||||
{
|
||||
// asst[0].switchAI("pirateFighterAI.js");
|
||||
}
|
||||
{ */
|
||||
asst[0].switchAI("pirateFighterAI.js");
|
||||
// }
|
||||
}
|
||||
|
||||
|
||||
@ -1148,7 +1148,6 @@ this._addPiratePack = function(pos,leader,lf,mf,hf,thug)
|
||||
lead[0].setBounty(60+system.government+Math.floor(Math.random()*8),"setup actions");
|
||||
var group = new ShipGroup("pirate pack",lead[0]);
|
||||
lead[0].group = group;
|
||||
// lead[0].switchAI("pirateFreighterAI.js"); // TODO: write AI
|
||||
for (var i = Math.floor(lf+(0.5+Math.random()-Math.random())); i > 0; i--)
|
||||
{
|
||||
this._addPirateAssistant("pirate-light-fighter",lead[0]);
|
||||
@ -1185,6 +1184,7 @@ this._addLightPirateLocal = function(pos)
|
||||
var lead = this._addPiratePack(pos,"pirate-light-freighter",2,1,-1,0);
|
||||
lead.homeSystem = system.ID;
|
||||
lead.destinationSystem = system.ID;
|
||||
lead.switchAI("pirateFreighterAI.js");
|
||||
}
|
||||
|
||||
|
||||
@ -1195,6 +1195,17 @@ this._addLightPirateRemote = function(pos)
|
||||
lead.homeSystem = this._nearbyDangerousSystem(system.info.government-1);
|
||||
this._setFuel(lead);
|
||||
lead.destinationSystem = system.ID;
|
||||
lead.switchAI("pirateFreighterAI.js");
|
||||
}
|
||||
|
||||
|
||||
this._addLightPirateOutbound = function(pos)
|
||||
{
|
||||
var lead = this._addPiratePack(pos,"pirate-light-freighter",2,1,-1,0);
|
||||
lead.destinationSystem = this._nearbySafeSystem(system.info.government+1);
|
||||
lead.fuel = 7
|
||||
lead.homeSystem = system.ID;
|
||||
lead.switchAI("pirateFreighterAI.js");
|
||||
}
|
||||
|
||||
|
||||
@ -1203,6 +1214,7 @@ this._addMediumPirateLocal = function(pos)
|
||||
var lead = this._addPiratePack(pos,"pirate-medium-freighter",3,2,0,1);
|
||||
lead.homeSystem = system.ID;
|
||||
lead.destinationSystem = system.ID;
|
||||
lead.switchAI("pirateFreighterAI.js");
|
||||
}
|
||||
|
||||
|
||||
@ -1213,6 +1225,7 @@ this._addMediumPirateRemote = function(pos)
|
||||
lead.homeSystem = this._nearbyDangerousSystem(system.info.government-1);
|
||||
this._setFuel(lead);
|
||||
lead.destinationSystem = system.ID;
|
||||
lead.switchAI("pirateFreighterAI.js");
|
||||
}
|
||||
|
||||
|
||||
@ -1221,6 +1234,7 @@ this._addHeavyPirateLocal = function(pos)
|
||||
var lead = this._addPiratePack(pos,"pirate-heavy-freighter",4,4,2,2);
|
||||
lead.homeSystem = system.ID;
|
||||
lead.destinationSystem = system.ID;
|
||||
lead.switchAI("pirateFreighterAI.js");
|
||||
}
|
||||
|
||||
|
||||
@ -1231,6 +1245,7 @@ this._addHeavyPirateRemote = function(pos)
|
||||
lead.homeSystem = this._nearbyDangerousSystem(system.info.government-1);
|
||||
this._setFuel(lead);
|
||||
lead.destinationSystem = system.ID;
|
||||
lead.switchAI("pirateFreighterAI.js");
|
||||
}
|
||||
|
||||
|
||||
|
@ -1719,18 +1719,26 @@ static ShipEntity *doOctreesCollide(ShipEntity *prime, ShipEntity *other);
|
||||
{
|
||||
[escorter switchAITo:autoAI];
|
||||
}
|
||||
|
||||
[UNIVERSE addEntity:escorter]; // STATUS_IN_FLIGHT, AI state GLOBAL
|
||||
|
||||
|
||||
[escorter setGroup:escortGroup];
|
||||
[escorter setOwner:self]; // mark self as group leader
|
||||
|
||||
|
||||
|
||||
if ([self status] == STATUS_DOCKED)
|
||||
{
|
||||
[[self owner] addShipToLaunchQueue:escorter withPriority:NO];
|
||||
}
|
||||
else
|
||||
{
|
||||
[UNIVERSE addEntity:escorter]; // STATUS_IN_FLIGHT, AI state GLOBAL
|
||||
[escortAI setState:@"FLYING_ESCORT"]; // Begin escort flight. (If the AI doesn't define FLYING_ESCORT, this has no effect.)
|
||||
[escorter doScriptEvent:OOJSID("spawnedAsEscort") withArgument:self];
|
||||
}
|
||||
|
||||
if([escorter heatInsulation] < [self heatInsulation]) [escorter setHeatInsulation:[self heatInsulation]]; // give escorts same protection as mother.
|
||||
if(([escorter maxFlightSpeed] < cruiseSpeed) && ([escorter maxFlightSpeed] > cruiseSpeed * 0.3))
|
||||
cruiseSpeed = [escorter maxFlightSpeed] * 0.99; // adapt patrolSpeed to the slowest escort but ignore the very slow ones.
|
||||
|
||||
[escortAI setState:@"FLYING_ESCORT"]; // Begin escort flight. (If the AI doesn't define FLYING_ESCORT, this has no effect.)
|
||||
[escorter doScriptEvent:OOJSID("spawnedAsEscort") withArgument:self];
|
||||
|
||||
if (bounty)
|
||||
{
|
||||
@ -12085,16 +12093,7 @@ Vector positionOffsetForShipInRotationToAlignment(ShipEntity* ship, Quaternion q
|
||||
{
|
||||
return; // has already entered a different wormhole
|
||||
}
|
||||
|
||||
if (replacing && ![[UNIVERSE sun] willGoNova] && [UNIVERSE sun] != nil)
|
||||
{
|
||||
/* Add a new ship to maintain quantities of standard ships, unless
|
||||
there's a nova in the works, the AI asked us not to, or we're in
|
||||
interstellar space.
|
||||
*/
|
||||
// now handled by system repopulator
|
||||
// [UNIVERSE witchspaceShipWithPrimaryRole:[self primaryRole]];
|
||||
}
|
||||
// Replacement ships now handled by system repopulator
|
||||
|
||||
// MKW 2011.02.27 - Moved here from ShipEntityAI so escorts reliably follow
|
||||
// mother in all wormhole cases, not just when the ship
|
||||
|
@ -45,6 +45,7 @@ MA 02110-1301, USA.
|
||||
- (void) enterPlayerWormhole;
|
||||
|
||||
- (void) wormholeEscorts;
|
||||
- (void) wormholeEntireGroup;
|
||||
|
||||
- (BOOL) suggestEscortTo:(ShipEntity *)mother;
|
||||
|
||||
|
@ -147,7 +147,6 @@
|
||||
- (void) performHyperSpaceExit;
|
||||
- (void) performHyperSpaceExitWithoutReplacing;
|
||||
- (void) wormholeGroup;
|
||||
- (void) wormholeEntireGroup;
|
||||
|
||||
- (void) commsMessage:(NSString *)valueString;
|
||||
- (void) commsMessageByUnpiloted:(NSString *)valueString;
|
||||
@ -696,6 +695,13 @@
|
||||
}
|
||||
|
||||
|
||||
- (void) wormholeEntireGroup
|
||||
{
|
||||
[self wormholeGroup];
|
||||
[self wormholeEscorts];
|
||||
}
|
||||
|
||||
|
||||
- (BOOL) suggestEscortTo:(ShipEntity *)mother
|
||||
{
|
||||
if (mother)
|
||||
@ -1635,13 +1641,6 @@
|
||||
}
|
||||
|
||||
|
||||
- (void) wormholeEntireGroup
|
||||
{
|
||||
[self wormholeGroup];
|
||||
[self wormholeEscorts];
|
||||
}
|
||||
|
||||
|
||||
- (void) commsMessage:(NSString *)valueString
|
||||
{
|
||||
[self commsMessage:valueString withUnpilotedOverride:NO];
|
||||
|
@ -1358,10 +1358,6 @@ NSDictionary *OOMakeDockingInstructions(StationEntity *station, HPVector coords,
|
||||
BOOL trader = [role isEqualToString:@"trader"];
|
||||
BOOL sunskimmer = ([role isEqualToString:@"sunskim-trader"]);
|
||||
ShipEntity *ship = nil;
|
||||
NSString *defaultRole = @"escort";
|
||||
NSString *escortRole = nil;
|
||||
NSString *escortShipKey = nil;
|
||||
NSDictionary *traderDict = nil;
|
||||
|
||||
if((trader && (randf() < 0.1)) || sunskimmer)
|
||||
{
|
||||
@ -1383,7 +1379,6 @@ NSDictionary *OOMakeDockingInstructions(StationEntity *station, HPVector coords,
|
||||
|
||||
if (ship)
|
||||
{
|
||||
traderDict = [ship shipInfoDictionary];
|
||||
if (![ship crew])
|
||||
[ship setCrew:[NSArray arrayWithObject:
|
||||
[OOCharacter randomCharacterWithRole: role
|
||||
@ -1422,64 +1417,9 @@ NSDictionary *OOMakeDockingInstructions(StationEntity *station, HPVector coords,
|
||||
unsigned escorts = [ship pendingEscortCount];
|
||||
if(escorts > 0)
|
||||
{
|
||||
escortRole = [traderDict oo_stringForKey:@"escort_role" defaultValue:nil];
|
||||
if (escortRole == nil)
|
||||
escortRole = [traderDict oo_stringForKey:@"escort-role" defaultValue:defaultRole];
|
||||
if (![escortRole isEqualToString: defaultRole])
|
||||
{
|
||||
if (![[UNIVERSE newShipWithRole:escortRole] autorelease])
|
||||
{
|
||||
escortRole = defaultRole;
|
||||
}
|
||||
}
|
||||
|
||||
escortShipKey = [traderDict oo_stringForKey:@"escort_ship" defaultValue:nil];
|
||||
if (escortShipKey == nil)
|
||||
escortShipKey = [traderDict oo_stringForKey:@"escort-ship"];
|
||||
|
||||
if (escortShipKey != nil)
|
||||
{
|
||||
if (![[UNIVERSE newShipWithName:escortShipKey] autorelease])
|
||||
{
|
||||
escortShipKey = nil;
|
||||
}
|
||||
}
|
||||
|
||||
while (escorts--)
|
||||
{
|
||||
ShipEntity *escort_ship;
|
||||
|
||||
if (escortShipKey)
|
||||
{
|
||||
escort_ship = [UNIVERSE newShipWithName:escortShipKey]; // retained
|
||||
}
|
||||
else
|
||||
{
|
||||
escort_ship = [UNIVERSE newShipWithRole:escortRole]; // retained
|
||||
}
|
||||
|
||||
if (escort_ship && [self fitsInDock:escort_ship])
|
||||
{
|
||||
if (![escort_ship crew] && ![escort_ship isUnpiloted])
|
||||
[escort_ship setCrew:[NSArray arrayWithObject:
|
||||
[OOCharacter randomCharacterWithRole: @"hunter"
|
||||
andOriginalSystem: [UNIVERSE systemSeed]]]];
|
||||
|
||||
[escort_ship setScanClass: [ship scanClass]];
|
||||
[escort_ship setCargoFlag: CARGO_FLAG_FULL_PLENTIFUL];
|
||||
[escort_ship setPrimaryRole:@"escort"];
|
||||
if ((sunskimmer || trader) && [escort_ship heatInsulation] < [ship heatInsulation])
|
||||
[escort_ship setHeatInsulation:[ship heatInsulation]];
|
||||
|
||||
[escort_ship setGroup:escortGroup];
|
||||
[escort_ship setOwner:ship];
|
||||
|
||||
[escort_ship switchAITo:@"escortAI.js"];
|
||||
[self addShipToLaunchQueue:escort_ship withPriority:NO];
|
||||
|
||||
}
|
||||
[escort_ship release];
|
||||
}
|
||||
[ship setOwner:self]; // makes escorts get added to station launch queue
|
||||
[ship setUpEscorts];
|
||||
[ship setOwner:ship];
|
||||
}
|
||||
|
||||
[ship setPendingEscortCount:0];
|
||||
|
@ -102,6 +102,7 @@ static JSBool ShipBroadcastDistressMessage(JSContext *context, uintN argc, jsval
|
||||
static JSBool ShipOfferToEscort(JSContext *context, uintN argc, jsval *vp);
|
||||
static JSBool ShipMarkTargetForFines(JSContext *context, uintN argc, jsval *vp);
|
||||
static JSBool ShipEnterWormhole(JSContext *context, uintN argc, jsval *vp);
|
||||
static JSBool ShipNotifyGroupOfWormhole(JSContext *context, uintN argc, jsval *vp);
|
||||
static JSBool ShipThrowSpark(JSContext *context, uintN argc, jsval *vp);
|
||||
|
||||
static JSBool ShipPerformAttack(JSContext *context, uintN argc, jsval *vp);
|
||||
@ -450,6 +451,7 @@ static JSFunctionSpec sShipMethods[] =
|
||||
{ "getShaders", ShipGetShaders, 0 },
|
||||
{ "hasRole", ShipHasRole, 1 },
|
||||
{ "markTargetForFines", ShipMarkTargetForFines, 0 },
|
||||
{ "notifyGroupOfWormhole", ShipNotifyGroupOfWormhole, 0 },
|
||||
{ "offerToEscort", ShipOfferToEscort, 1 },
|
||||
{ "patrolReportIn", ShipPatrolReportIn, 1},
|
||||
{ "performAttack", ShipPerformAttack, 0 },
|
||||
@ -3024,6 +3026,21 @@ static JSBool ShipEnterWormhole(JSContext *context, uintN argc, jsval *vp)
|
||||
}
|
||||
|
||||
|
||||
static JSBool ShipNotifyGroupOfWormhole(JSContext *context, uintN argc, jsval *vp)
|
||||
{
|
||||
OOJS_PROFILE_ENTER
|
||||
|
||||
ShipEntity *thisEnt = nil;
|
||||
GET_THIS_SHIP(thisEnt);
|
||||
|
||||
[thisEnt wormholeEntireGroup];
|
||||
|
||||
OOJS_RETURN_VOID;
|
||||
|
||||
OOJS_PROFILE_EXIT
|
||||
}
|
||||
|
||||
|
||||
static JSBool ShipThrowSpark(JSContext *context, uintN argc, jsval *vp)
|
||||
{
|
||||
OOJS_PROFILE_ENTER
|
||||
|
Loading…
x
Reference in New Issue
Block a user