Path |
Scripts/DayDiplomacy_Citizenships.js |
"use strict";
this.name = "DayDiplomacy_060_Citizenships";
this.author = "Loic Coissard, David Pradier";
// noinspection JSUnusedGlobalSymbols Used by Oolite itself
this.copyright = "(C) 2019 Loic Coissard, David Pradier";
// noinspection JSUnusedGlobalSymbols Used by Oolite itself
this.licence = "CC-NC-by-SA 4.0";
this.description = "This script is the citizenships engine.";
/* ************************** Public functions ********************************************************/
/**
* This formula would put the US citizenship at 57.000.000 USD in 2016 and the french one at 37.000.000 USD.
* Remember everything is a lot more expensive in space!
* @param {System} aSystem
* @returns {number} the price to acquire or renounce this citizenship in credits
* @lends worldScripts.DayDiplomacy_060_Citizenships.$getCitizenshipPrice
*/
this.$getCitizenshipPrice = function (aSystem) {
// productivity is in 10^6 credits units, and population is in 10^8 people units
// the price is 1000 years of 1-person productivity: prod*10^6 *1000 / (pop *10^8) = 10*prod/pop
return Math.round(10 * aSystem.productivity / aSystem.population * 10) / 10;
};
/**
* This formula would put the US 1-day visa at 15.600 USD in 2016 and the french one at 10.100 USD.
* Remember everything is a lot more expensive in space!
* @param {SystemInfo} aSystem
* @returns {number} the price to acquire or renounce the visa for 1 day in credits
* @lends worldScripts.DayDiplomacy_060_Citizenships.$getVisaPrice
*/
this.$getVisaPrice = function (aSystem) {
// the price is 100 days of 1-person productivity: prod*10^6 / (pop*10^8) /365 * 100 = prod/pop/365
return Math.round(aSystem.productivity / aSystem.population / 365 * 10) / 10;
};
/**
* @param {int} galaxyID
* @param {int} systemID
* @returns {boolean} true if the player has the citizenship
* @lends worldScripts.DayDiplomacy_060_Citizenships.$hasPlayerCitizenship
*/
this.$hasPlayerCitizenship = function (galaxyID, systemID) {
var citizenships = this._citizenships;
var i = citizenships.length;
while (i--) {
var planetarySystem = citizenships[i];
if (planetarySystem.galaxyID === galaxyID && planetarySystem.systemID === systemID) {
return true;
}
}
return false;
};
/**
*
* @param systemID
* @return {boolean}
* @lends worldScripts.DayDiplomacy_060_Citizenships.$hasPlayerVisa
*/
this.$hasPlayerVisa = function(systemID) {
this._cleaningVisas();
return this._visas.hasOwnProperty(systemID);
};
/**
* @param {PlanetarySystem[]} citizenships
* @returns {string} a displayable list of citizenships
* @lends worldScripts.DayDiplomacy_060_Citizenships.$buildCitizenshipsString
*/
this.$buildCitizenshipsString = function (citizenships) {
var result = "";
var i = citizenships.length;
while (i--) {
result += citizenships[i].name + ", ";
}
if (result.length) { // We delete the comma at the end of the string
result = result.substring(0, result.length - 2);
}
return result;
};
this.$buildVisasString = function () {
this._cleaningVisas(); // Cleaning obsolete visas before displaying visas
var visas = this._visas;
var result = "";
var now = clock.seconds;
for (var systemID in visas) {
if (visas.hasOwnProperty(systemID)) {
var systemInfo = System.infoForSystem(system.info.galaxyID, systemID);
var remainingTime = visas[systemID] - now;
var remainingHours = Math.floor(remainingTime / 3600);
var remainingMinutes = Math.floor((remainingTime - remainingHours * 3600) / 60);
result += "\n " + systemInfo.name + ": " + remainingHours + " h " + remainingMinutes + " min" + ","
}
}
if (result.length) { // We delete the comma at the end of the string
result = result.substring(0, result.length - 1);
} else {
result = "none";
}
return result;
};
/**
* Allows the script which name is given as argument to be called through the method $playerCitizenshipsUpdated
* each time the player citizenships are updated. The script must implement that method: this.$playerCitizenshipsUpdated = function(citizenships) {}
* @param {string} scriptName the script.name
* @lends worldScripts.DayDiplomacy_060_Citizenships.$subscribeToPlayerCitizenshipsUpdates
*/
this.$subscribeToPlayerCitizenshipsUpdates = function (scriptName) {
(this._playerCitizenshipsUpdatesSubscribers || (this._playerCitizenshipsUpdatesSubscribers = [])).push(scriptName);
};
/* ************************** OXP private functions *******************************************************/
/**
* @param {number} price
* @returns {boolean} true if there was enough money to pay
* @private
*/
this._payIfCapable = function(price) {
if (player.credits >= price) {
player.credits -= price;
return true;
}
return false;
};
/**
* Allows the player to acquire a citizenship
* @param {int} galaxyID the galaxyID of the citizenship
* @param {int} systemID the systemID of the citizenship
* @returns {boolean} true if the player had the money to acquire the citizenship
* @private
*/
this._buyCitizenship = function (galaxyID, systemID) {
if (this._payIfCapable(this.$getCitizenshipPrice(system))) { // FIXME incorrect price if not asking for the current system
this._citizenships.push({
"galaxyID": galaxyID,
"systemID": systemID,
"name": this._Systems.$retrieveNameFromSystem(galaxyID, systemID)
});
return true;
}
return false;
};
/**
* Allows the player to renounce a citizenship
* @param {int} galaxyID the galaxyID of the citizenship
* @param {int} systemID the systemID of the citizenship
* @returns {boolean} true if the citizenship has been renounced
* @private
*/
this._loseCitizenship = function (galaxyID, systemID) {
if (this._payIfCapable(this.$getCitizenshipPrice(system))) { // FIXME incorrect price if not asking for the current system
var citizenships = this._citizenships;
var i = citizenships.length;
while (i--) {
var planetarySystem = citizenships[i];
if (planetarySystem.galaxyID === galaxyID && planetarySystem.systemID === systemID) {
if (this._flag.galaxyID === galaxyID && this._flag.systemID === systemID) {
delete this._flag.galaxyID;
delete this._flag.systemID;
delete this._flag.name;
}
citizenships.splice(i, 1);
return true;
}
}
}
return false;
};
/**
*
* @private
*/
this._cleaningVisas = function () {
var now = clock.seconds;
var visas = this._visas;
for (var systemID in visas) {
if (visas.hasOwnProperty(systemID)) {
if (visas[systemID] <= now) {
delete visas[systemID];
}
}
}
};
/**
* Displays the citizenship's screen allowing the player to buy and lose citizenships, to display their citizenships
* and to choose which citizenship is displayed.
* @param {boolean} notEnoughMoney set to true if a previous command failed because the player had not enough money
* @private
*/
this._runCitizenship = function (notEnoughMoney) {
player.ship.hudHidden || (player.ship.hudHidden = true);
var info = system.info;
var currentGalaxyID = info.galaxyID;
var currentSystemID = info.systemID;
var currentSystemName = this._Systems.$retrieveNameFromSystem(currentGalaxyID, currentSystemID);
var currentCitizenships = this._citizenships;
var i = currentCitizenships.length;
var price = this.$getCitizenshipPrice(system);
var currentFlag = this._flag;
// Exit choice, and displayed information
var opts = {
screenID: "DiplomacyCitizenshipsScreenId",
title: "Embassy",
allowInterrupt: true,
exitScreen: "GUI_SCREEN_INTERFACES",
choices: {"6_EXIT": "Exit"},
message: "Your credits: " + (Math.round(player.credits * 10) / 10) + " ₢\n"
+ (notEnoughMoney ? "You had not enough money to do this.\n" : "")
+ "Your flag: " + (currentFlag.name || "stateless")
+ "\nYour passports: " + (i ? this.$buildCitizenshipsString(currentCitizenships) : "none") // FIXME "none" should be in the $build
+ "\nYour visas: " + this.$buildVisasString()
};
// Choices to acquire or renounce the system citizenship
var currentChoices = opts.choices;
if (this.$hasPlayerCitizenship(currentGalaxyID, currentSystemID)) {
currentChoices["2_LOSE"] = "Renounce your " + currentSystemName + "ian passport for a cost of " + price + " ₢";
} else {
currentChoices["1_BUY"] = "Acquire " + currentSystemName + " citizenship for a cost of " + price + " ₢";
}
// Choosing which among the owned citizenships to display as the flagship
while (i--) {
var planetarySystem = currentCitizenships[i];
// We don't propose the current flagship
if (!(currentFlag.galaxyID === planetarySystem.galaxyID && currentFlag.systemID === planetarySystem.systemID)) {
currentChoices["3_DISPLAY_" + planetarySystem.galaxyID + "_" + planetarySystem.systemID] = "Make your ship display your " + planetarySystem.name + " flag";
}
}
// Choice to hide the flagship
if (currentFlag.name) {
currentChoices["4_HIDEFLAG"] = "Hide your flag";
}
// Choices to buy a visa for the neighbouring, non-enemy, dictator, communist or corporate systems
var theseSystems = info.systemsInRange(), j = theseSystems.length;
if (j) {
var systemsActorIdsByGalaxyAndSystemId = this._Systems.$getSystemsActorIdsByGalaxyAndSystemId();
var war = worldScripts.DayDiplomacy_040_WarEngine;
while (j--) {
var thatSystemInfo = theseSystems[j];
var gov = thatSystemInfo.government;
if (gov === 3 || gov === 4 || gov === 7) { // dictator, communist, corporate
var isEnemy = war.$areActorsWarring(
// current system ActorId
systemsActorIdsByGalaxyAndSystemId[currentGalaxyID][currentSystemID],
// other system ActorId
systemsActorIdsByGalaxyAndSystemId[currentGalaxyID][thatSystemInfo.systemID]
);
if (!isEnemy) {
if (this.$hasPlayerVisa(thatSystemInfo.systemID)) {
currentChoices["5_BUYVISA_" + thatSystemInfo.systemID] =
"Extend your visa for " + thatSystemInfo.name + " by 24 hours for a cost of " + this.$getVisaPrice(thatSystemInfo) + " ₢";
} else {
currentChoices["5_BUYVISA_" + thatSystemInfo.systemID] =
"Buy 24 hours of visa for " + thatSystemInfo.name + " for a cost of " + this.$getVisaPrice(thatSystemInfo) + " ₢";
}
}
}
}
}
mission.runScreen(opts, this._F4InterfaceCallback.bind(this));
};
this._add1DayVisa = function (systemID) {
var now = clock.seconds;
if (this._visas[systemID] > now) {
this._visas[systemID] += 3600 * 24;
} else {
this._visas[systemID] = now + 3600 * 24;
}
};
/**
Calls the necessary functions depending on the player's choice in the F4 interface
@param {String} choice - predefined values: 1_BUY, 2_LOSE, 3_DISPLAY_{int}, 4_HIDEFLAG, 5_EXIT
@private
*/
this._F4InterfaceCallback = function (choice) {
if (choice === "1_BUY" || choice === "2_LOSE") {
var info = system.info;
var success = choice === "1_BUY" ? this._buyCitizenship(info.galaxyID, info.systemID) : this._loseCitizenship(info.galaxyID, info.systemID);
if (success) {
this._publishNewsSubscribers();
}
this._runCitizenship(!success);
} else {
var currentFlag = this._flag;
if (choice === "4_HIDEFLAG") {
delete currentFlag.galaxyID;
delete currentFlag.systemID;
delete currentFlag.name;
this._runCitizenship(false);
} else if (choice !== null && choice.substring(0, 10) === "3_DISPLAY_") {
var galaxyID = parseInt(choice.substring(10, 11)), systemID = parseInt(choice.substring(12));
currentFlag.galaxyID = galaxyID;
currentFlag.systemID = systemID;
currentFlag.name = this._Systems.$retrieveNameFromSystem(galaxyID, systemID);
this._runCitizenship(false);
} else if (choice !== null && choice.substring(0, 10) === "5_BUYVISA_") {
var systemID = parseInt(choice.substring(10));
var thatSystemInfo = System.infoForSystem(system.info.galaxyID, systemID);
var paid = this._payIfCapable(this.$getVisaPrice(thatSystemInfo));
if (paid) {
this._add1DayVisa(systemID);
}
this._runCitizenship(!paid);
}
} // else EXIT
};
/**
* Hides the HUD and displays the F4 interface
* @private
*/
this._displayF4Interface = function () {
this._runCitizenship(false);
};
/**
* Displays the citizenship line in the F4 interface
* @private
*/
this._initF4Interface = function () {
// No Embassy district in anarchies
if (system.government === 0) return;
player.ship.dockedStation.setInterface("DiplomacyCitizenships",
{
title: "Embassy district",
category: "Diplomacy",
summary: "You may see current citizenships",
callback: this._displayF4Interface.bind(this)
});
};
/**
* Calls the method $playerCitizenshipsUpdated() for each subscribed script with the current citizenships list as argument.
* @private
*/
this._publishNewsSubscribers = function () {
var subscribers = this._playerCitizenshipsUpdatesSubscribers, l = subscribers.length,
citizenships = this._citizenships;
while (l--) {
// noinspection JSUnresolvedFunction This method must be implemented in the subscribed scripts.
worldScripts[subscribers[l]].$playerCitizenshipsUpdated(citizenships);
}
};
/**
* This function makes sure that the player is considered as a fugitive in an enemy system.
* @private
*/
this._checkPlayerStatusInWar = function () {
var worldScriptsVar = worldScripts;
var systemInfo = system.info;
var flag = this._flag;
var systemsActorIdsByGalaxyAndSystemId = worldScriptsVar.DayDiplomacy_010_Systems.$getSystemsActorIdsByGalaxyAndSystemId();
var inEnemySystem = flag.systemID && worldScriptsVar.DayDiplomacy_040_WarEngine.$areActorsWarring(
// current system ActorId
systemsActorIdsByGalaxyAndSystemId[systemInfo.galaxyID][systemInfo.systemID],
// current flag ActorId
systemsActorIdsByGalaxyAndSystemId[flag.galaxyID][flag.systemID]
);
var comingFromEnemySystem = this._peacefulSystemsBounty.value !== null;
if (inEnemySystem) {
if (!comingFromEnemySystem) { // Entering enemy system
this._peacefulSystemsBounty.value = player.bounty;
}
player.bounty = 200;
player.commsMessage("It seems we are in an enemy system, fights are probable...");
} else if (comingFromEnemySystem) { // Exiting enemy system
player.bounty = this._peacefulSystemsBounty.value;
this._peacefulSystemsBounty.value = null;
}
};
/* ************************** Oolite events ***************************************************************/
// noinspection JSUnusedLocalSymbols Called by Oolite itself
/**
* Displays the citizenship's line in the F4 interface when the player is docked.
* @param {Station} station an Oolite object where the ship is docked. We don't use it.
*/
this.shipDockedWithStation = function (station) {
this._initF4Interface();
};
// noinspection JSUnusedGlobalSymbols Called by Oolite itself
/**
* We stop hiding the HUD when we exit our citizenship interface
*/
this.missionScreenEnded = function () {
player.ship.hudHidden = false;
};
/**
*
* @private
*/
this._setStationsVisaRequirements = function () {
var gov = system.government;
if (gov === 3 || gov === 4 || gov === 7) {
var checker = function (ship) {
if (!(ship instanceof PlayerShip)) { // Only for the player ship
return true;
}
if (worldScripts.DayDiplomacy_060_Citizenships._citizenships.length) {
// No problem if the player has a citizenship
return true;
}
if (worldScripts.DayDiplomacy_060_Citizenships.$hasPlayerVisa(system.info.systemID)) {
// No problem if the player has a visa
return true;
}
this.commsMessage("WARNING - This station is accessible only to citizens and visa holders, Commander.", player.ship);
return false;
};
var ss = system.stations, z = ss.length;
while (z--) {
var station = ss[z];
var al = station.allegiance;
if (al === "galcop" || al === "neutral") {
var ses = station.subEntities, y = ses.length;
while (y--) {
var se = ses[y];
if (se.isDock) {
se.script.acceptDockingRequestFrom = checker.bind(station);
break;
}
}
}
}
}
};
// noinspection JSUnusedGlobalSymbols Called by Oolite itself
this.shipExitedWitchspace = function () {
this._checkPlayerStatusInWar();
this._setStationsVisaRequirements();
};
// noinspection JSUnusedGlobalSymbols Called by Oolite itself
/**
*
* @param {Station}station - the station from which the ship is launched
*/
this.shipLaunchedFromStation = function (station) {
this._checkPlayerStatusInWar();
};
/**
* Loads the player citizenship from the save file, loads the scripts which are subscribed to the
* playerCitizenshipsUpdates, and initialises the F4 interface.
* @private
*/
this._startUp = function () {
worldScripts.XenonUI && worldScripts.XenonUI.$addMissionScreenException("DiplomacyCitizenshipsScreenId");
worldScripts.XenonReduxUI && worldScripts.XenonReduxUI.$addMissionScreenException("DiplomacyCitizenshipsScreenId");
this._Systems = worldScripts.DayDiplomacy_010_Systems;
var engine = worldScripts.DayDiplomacy_000_Engine;
// {String[]} _playerCitizenshipsUpdatesSubscribers - an array containing the names of the scripts which have subscribed to receive notifications when the player citizenships have changed.
this._playerCitizenshipsUpdatesSubscribers || (this._playerCitizenshipsUpdatesSubscribers = []);
/**
* The flag of the player ship, saved. None by default.
* @type {PlanetarySystem}
* @private
*/
this._flag = engine.$initAndReturnSavedData("flag", {});
/**
* The value is only set when the player is in an enemy system; else it is 'null'.
* When beginning to use the Diplomacy Oxp, the player is not in an enemy system.
* @type {Object}
* @param {int} value
* @private
*/
this._peacefulSystemsBounty = engine.$initAndReturnSavedData("peacefulSystemsBounty", {value: null});
/**
* The object in which the player citizenships are saved. That object is saved into the saveGame file.
* @type {PlanetarySystem[]}
*/
this._citizenships = engine.$initAndReturnSavedData("citizenships", []);
var visasDefaultValue = {};
var gov = system.government;
if (gov == 3 || gov == 4 || gov == 7) {
visasDefaultValue[system.info.systemID] = clock.seconds + 24*3600;
}
/**
* The object in which the player visas are saved. That object is saved into the saveGame file. The first int is the systemID, the second the end date of the visa in seconds.
* The first time the Diplomacy OXP is used, if a visa is needed in the current system, we give the player a 1-day visa.
* @type {Object<int,int>}
*/
this._visas = engine.$initAndReturnSavedDataAndInitialize("visas", visasDefaultValue, function() {
worldScripts.DayDiplomacy_015_GNN.$publishNews(
"Serious news! To ensure their security, corporate systems have agreed to require a visa for all undocumented travelers."
+" Are you up-to-date on your citizenship papers, Commanders?\n"
+"\nIn a shocking political twist, dictatorships and communist systems have happily adopted the same law."
+" The President of Ceesxe, the Preeminent Corporate Planet, told us: \"We are appalled that our well-meant initiatives and technologies are copied by rogue governments.\"\n"
+"\nTo avoid an economic freeze due to the newly introduced laws, all pilots currently in a system requiring a visa will be provided a 1-day visa free of charge."
+" The Ceesxe President confided in us: \"The first shot is always free. That's only good business, after all.\"\n"
+" What he meant by this, the truth is, we don't know.");
});
this._setStationsVisaRequirements();
this._initF4Interface();
delete this._startUp; // No need to startup twice
};
this.startUp = function () {
worldScripts.DayDiplomacy_000_Engine.$subscribe(this.name);
delete this.startUp; // No need to startup twice
}; |
Scripts/DayDiplomacy_Economy.js |
"use strict";
this.name = "DayDiplomacy_030_EconomyEngine";
this.author = "David (Day) Pradier";
// noinspection JSUnusedGlobalSymbols Used by Oolite itself
this.copyright = "(C) 2017 David Pradier";
// noinspection JSUnusedGlobalSymbols Used by Oolite itself
this.licence = "CC-NC-by-SA 4.0";
this.description = "This script is the economy engine of the Diplomacy OXP. It makes systems tax themselves." +
" The idea here is to use the system GDP, called 'productivity' in Oolite," +
" and a 'tax level' on GDP which adds to the system's government's 'treasury'.";
/* Credit monetary policy is such that the total number of credits in the Ooniverse is always the same.
This is needed to avoid game imbalances leading to exploding monetary mass, or monetary mass converging to zero.
This implies that the money cannot be produced, destroyed or counterfeited, which is a mystery in itself. */
/**
*
* @type {{"0": number, "1": number, "2": number, "3": number, "4": number, "5": number, "6": number, "7": number}}
* @lends worldScripts.DayDiplomacy_030_EconomyEngine.$GOVERNMENT_DEFAULT_TAX_LEVEL;
*/
this.$GOVERNMENT_DEFAULT_TAX_LEVEL = {
"0": 0.0, // Anarchy => no tax
"1": 0.3, // Feudal => not everybody is taxed
"2": 0.1, // Multi-government => tax avoiding is rampant
"3": 0.2, // Dictator => feeble taxes
"4": 0.5, // Communist => major taxes, but those systems are not crumbling
"5": 0.1, // Confederacy => tax avoiding is rampant
"6": 0.5, // Democracy => major taxes, but those systems are not crumbling
"7": 0.1 // Corporate => tax avoiding is rampant
};
this.$moveProductivityInPercentage = function(fromSystemActor, percentage) {
// FIXME 0.15 TODO
};
this.$moveProductivityInCredits = function(fromSystemActor, creditsNb) {
// FIXME 0.15 TODO
};
this.$moveProductivityToNeighborsInPercentage = function(fromSystemActor, percentage) {
// FIXME 0.15 TODO
};
this.$moveProductivityToNeighborsInCredits = function(fromSystemActor, creditsNb) {
// FIXME 0.15 TODO
};
this.$moveProductivityToNeighborsDependingOnDistanceInPercentage = function(fromSystemActor, percentage) {
// FIXME 0.15 TODO
};
this.$moveProductivityToNeighborsDependingOnDistanceInCredits = function(fromSystemActor, creditsNb) {
// FIXME 0.15 TODO
};
/* ************************** Oolite events ***************************************************************/
this._startUp = function () {
var engine = worldScripts.DayDiplomacy_000_Engine;
// Not initializing if already done.
if (engine.$getEventTypes().indexOf("SELFTAX") !== -1) {
return;
}
// This eventType means a system government taxes the system GDP (economic output) to fund its treasury.
engine.$addEventType("SELFTAX", 0);
/**
* @function
* @param {Actor}aSystem
*/
var diplomacyTaxInitAction = function diplomacyTaxInitAction(aSystem) {
var that = diplomacyTaxInitAction;
var engine = that.engine || (that.engine = worldScripts.DayDiplomacy_000_Engine);
var taxLevel = that.taxLevel || (that.taxLevel = worldScripts.DayDiplomacy_030_EconomyEngine.$GOVERNMENT_DEFAULT_TAX_LEVEL);
var sys = that.sys || (that.sys = System);
var cloc = that.cloc || (that.cloc = clock);
var ourSystemInOolite = sys.infoForSystem(aSystem.galaxyNb, aSystem.systemId);
var government = ourSystemInOolite.government;
engine.$setField(aSystem, "government", government);
// Necessary for alliancesAndWars. Bad location but avoids other system initialization :/
// FIXME 0.perfectstyle fields should be inited in the systems part. Make it all fields?
// FIXME 0.f move treasury and tax level to a F4 Diplomacy system information including the history.
// Or use the new description system?
// FIXME Should the Actor aSystem be typed ActorSystem? mwofff
engine.$setField(aSystem, "name", ourSystemInOolite.name);
engine.$setField(aSystem, "taxLevel", taxLevel[government]);
engine.$setField(aSystem, "treasury", 0); // Everybody begins with treasury = 0.
engine.$setField(aSystem, "lastTaxDate", cloc.seconds);
};
var functionId = engine.$getNewFunctionId();
engine.$setFunction(functionId, diplomacyTaxInitAction);
engine.$setInitAction(engine.$buildAction(engine.$getNewActionId(), "SELFTAX", "SYSTEM", functionId));
// Recurrent tax.
var diplomacyTaxRecurrentAction = function diplomacyTaxRecurrentAction(aSystem) {
var that = diplomacyTaxRecurrentAction;
var engine = that.engine || (that.engine = worldScripts.DayDiplomacy_000_Engine);
var sys = that.sys || (that.sys = System);
var cloc = that.cloc || (that.cloc = clock);
var ourSystemInOolite = sys.infoForSystem(aSystem.galaxyNb, aSystem.systemId);
var now = cloc.seconds;
engine.$setField(aSystem, "treasury", aSystem.treasury + Math.floor(ourSystemInOolite.productivity * (now - parseInt(aSystem.lastTaxDate)) / 31.5576 * aSystem.taxLevel));
engine.$setField(aSystem, "lastTaxDate", now);
};
var fid = engine.$getNewFunctionId();
engine.$setFunction(fid, diplomacyTaxRecurrentAction);
engine.$setRecurrentAction(engine.$buildAction(engine.$getNewActionId(), "SELFTAX", "SYSTEM", fid));
delete this._startUp; // No need to startup twice
};
this.guiScreenChanged = function(to, from) {
if (to == "GUI_SCREEN_SYSTEM_DATA") {
var targetSystem = worldScripts.DayDiplomacy_010_Systems.$retrieveActorFromSystem(system.info.galaxyID, player.ship.targetSystem);
mission.addMessageText("Tax level: " + targetSystem.taxLevel*100 + "% Treasury: " + Math.floor(targetSystem.treasury/1000000) + " M€");
}
};
this.startUp = function() {
worldScripts.DayDiplomacy_000_Engine.$subscribe(this.name);
delete this.startUp; // No need to startup twice
}; |
Scripts/DayDiplomacy_Engine.js |
"use strict";
this.name = "DayDiplomacy_000_Engine";
this.author = "David (Day) Pradier";
// noinspection JSUnusedGlobalSymbols Used by Oolite itself
this.copyright = "(C) 2017 David Pradier";
// noinspection JSUnusedGlobalSymbols Used by Oolite itself
this.licence = "CC-NC-by-SA 4.0";
this.description = "This script is the engine of the Diplomacy OXP.";
/* ************************** Closures ********************************************************************/
this._missionVariables = missionVariables;
this._clock = clock;
this._JSON = JSON;
/* ************************** Engine **********************************************************************/
// FIXME use the debugger to set _debug to true
this._debug = false;
/**
* Loads state from the saved game
* @param toBeModifiedState
* @param sourceState
* @private
*/
this._loadState = function (toBeModifiedState, sourceState) {
for (var id in sourceState) {
if (sourceState.hasOwnProperty(id)) { // Avoiding prototypes' fields
toBeModifiedState[id] = sourceState[id];
}
}
};
/**
*
* @return {{shortStack: Array, eventsToPublish: {}, initActions: {}, eventMaxId: number, actorTypes: Array, initActionsByType: {}, recurrentActionsByType: {}, actorsEvents: {}, functionMaxId: number, eventTypes: Array, actorMaxId: number, actors: {}, recurrentActions: {}, responseMaxId: number, actionMaxId: number, responsesByType: {}, actorsByType: {}, eventsToPublishNextTurn: {}, responses: {}, currentActorType: string, currentEventType: string, events: {}}}
* @private
* @lends worldScripts.DayDiplomacy_000_Engine._getInitState
*/
this._getInitState = function () {
return {
/** @type {Object.<ActorId,Actor>}*/
actors: {},
/** @type {Object.<ActionId,Action>}*/
initActions: {},
/** @type {Object.<ActionId,Action>}*/
recurrentActions: {},
/** @type {Object.<EventId,DiplomacyEvent>}*/
events: {},
/** @type {Object.<ResponseId,DiplomacyResponse>}*/
responses: {},
/** @type {Object.<ActorType,ActorId[]>}*/
actorsByType: {},
/** @type {Object.<ActorType,ActionId[]>}*/
initActionsByType: {},
/** @type {Object.<EventType,Object.<ActorType,ActionId[]>>}*/
recurrentActionsByType: {},
/** @type {Object.<EventType,Object.<ActorType,ResponseId[]>>}*/
responsesByType: {},
/** @type {int} */
actorMaxId: 1,
/** Useful to remove recurrentActions and initActions.
* @type {int} */
actionMaxId: 1,
/** @type {int} */
eventMaxId: 1,
/** @type {int} */
responseMaxId: 1,
/** @type {int} */
functionMaxId: 1,
/** @type {EventType[]} */
eventTypes: [],
/** @type {ActorType[]} */
actorTypes: [],
/** @type {Object.<ActorId,EventId[]>}*/
actorsEvents: {},
/** @type {Object.<EventType,EventId[]>}*/
eventsToPublish: {},
/** @type {Object.<EventType,EventId[]>}*/
eventsToPublishNextTurn: {},
/** @type EventType */
currentEventType: "",
/** @type ActorType */
currentActorType: "",
/** @type {Object[]} */
shortStack: []
};
};
// FIXME should _State be of a defined state? That would be what would make stable the savefile... ?
/**
* @type {{shortStack: Array, eventsToPublish: {}, initActions: {}, eventMaxId: number, actorTypes: Array, initActionsByType: {}, recurrentActionsByType: {}, actorsEvents: {}, functionMaxId: number, eventTypes: Array, actorMaxId: number, actors: {}, recurrentActions: {}, responseMaxId: number, actionMaxId: number, responsesByType: {}, actorsByType: {}, eventsToPublishNextTurn: {}, responses: {}, currentActorType: string, currentEventType: string, events: {}}}
* @private
* @lends worldScripts.DayDiplomacy_000_Engine._State
*/
this._State = this._getInitState();
/**
* @type {Object.<FunctionId,function>}
* @private
*/
this._Functions = {};
/**
* @return {ActorId}
* @lends worldScripts.DayDiplomacy_000_Engine.$getNewActorId
*/
this.$getNewActorId = function () {
return "DAr_" + this._State.actorMaxId++;
};
/**
* @return {ResponseId}
* @lends worldScripts.DayDiplomacy_000_Engine.$getNewResponseId
*/
this.$getNewResponseId = function () {
return "DR_" + this._State.responseMaxId++;
};
/**
* @return {ActionId}
* @lends worldScripts.DayDiplomacy_000_Engine.$getNewActionId
*/
this.$getNewActionId = function () {
return "DAn_" + this._State.actionMaxId++;
};
/**
* @return {FunctionId}
* @lends worldScripts.DayDiplomacy_000_Engine.$getNewFunctionId
*/
this.$getNewFunctionId = function () {
return "DFn_" + this._State.functionMaxId++;
};
/**
* @return {EventId}
* @lends worldScripts.DayDiplomacy_000_Engine.$getNewEventId
*/
this.$getNewEventId = function () {
return "DEt_" + this._State.eventMaxId++;
};
/**
* An action, whether it is init or recurrent isn't put into the History. Only Events are.
* @param {ActionId}id -
* @param {EventType}eventType - is used to order the actions and events execution. For a same eventType, Actions are executed before Events.
* @param {ActorType}actorType - Only actors of the type will execute the action.
* @param {FunctionId} actionFunctionId - the id of a function which must take one and only one argument: the actor which will "act".
* @return {Action}
* @lends worldScripts.DayDiplomacy_000_Engine.$buildAction
*/
this.$buildAction = function (id, eventType, actorType, actionFunctionId) {
/** @type {Action} */
return {id: id, eventType: eventType, actorType: actorType, actionFunctionId: actionFunctionId};
};
/**
* To copy before modifying
* @return {EventType[]}
* @lends worldScripts.DayDiplomacy_000_Engine.$getEventTypes
*/
this.$getEventTypes = function () {
return this._State.eventTypes;
};
/**
* @return {Object<FunctionId, function>}
* @lends worldScripts.DayDiplomacy_000_Engine.$getFunctions
*/
this.$getFunctions = function () {
return this._Functions;
};
/**
* @return {Object<EventId,DiplomacyEvent>}
* @lends worldScripts.DayDiplomacy_000_Engine.$getEvents
*/
this.$getEvents = function () {
return this._State.events;
};
/**
* @param {ActorId} actorId
* @return {EventId[]}
* @lends worldScripts.DayDiplomacy_000_Engine.$getActorEvents
*/
this.$getActorEvents = function (actorId) {
return this._State.actorsEvents[actorId] || [];
};
/**
* Make sure you don't modify that or its content. Copy it before if you need to modify it.
* @return {ActorType[]} The ActorType list
* @lends worldScripts.DayDiplomacy_000_Engine.$getActorTypes
*/
this.$getActorTypes = function () {
return this._State.actorTypes;
};
/**
* @param {ActorType} actorType
* @returns {ActorId[]} the list of actorId having the type given as parameter
* @lends worldScripts.DayDiplomacy_000_Engine.$getActorsIdByType
*/
this.$getActorsIdByType = function (actorType) {
return this._State.actorsByType[actorType];
};
/**
* @name $getActors
* @returns {Object.<ActorId,Actor>} - an object with {@link ActorId} as keys and as value the corresponding {@link Actor}
* @lends worldScripts.DayDiplomacy_000_Engine.$getActors
*/
this.$getActors = function () {
return this._State.actors;
};
/**
* A planetary system or an alliance, or whatever you wish :)
* An actor is {id:id, actorType:actorType, responsesIdByEventType:{eventType:[responseIds]}, observers:{actorType:[actorIds]}}
* @param {ActorType} actorType
* @param {ActorId} id
* @return {Actor}
* @lends worldScripts.DayDiplomacy_000_Engine.$buildActor
*/
this.$buildActor = function (actorType, id) {
/** @type {Actor} */
return {id: id, actorType: actorType, responsesIdByEventType: {}, observers: {}};
};
/**
* @param {EventId} id
* @param {EventType}eventType
* @param {ActorId} actorId
* @param {Object[]} args Have to be compatible with our implementation of JSON stringify/parse. Those are the information/arguments which will be given to the response function.
* @return {DiplomacyEvent}
*/
this.$buildEvent = function (id, eventType, actorId, args) {
return {id: id, eventType: eventType, actorId: actorId, args: args, date:0};
};
/**
*
* @param {Actor} anActor
* @param {ActorType} observersActorType
* @returns {ActorId[]} the list of the actorId's of the observers of the given actor, which are of the given type
* @lends worldScripts.DayDiplomacy_000_Engine.$getObservers
*/
this.$getObservers = function (anActor, observersActorType) {
return anActor.observers[observersActorType];
};
/**
* @param {Actor} anActor
* @param {Action} anAction
*/
this.$letActorExecuteAction = function (anActor, anAction) {
this._Functions[anAction.actionFunctionId](anActor);
};
/**
*
* @param {ActorId} actorId
* @param {EventType}anEventType
* @param {Object[]} someArgs
* @lends worldScripts.DayDiplomacy_000_Engine.$makeActorEventKnownToUniverse
*/
this.$makeActorEventKnownToUniverse = function (actorId, anEventType, someArgs) {
this._record(this.$buildEvent(this.$getNewEventId(), anEventType, actorId, someArgs));
};
// this.$Actor.prototype.actNextTurn = function (anEventType, someArgs) {
// this.recordForNextTurn({id:eventId, eventType: anEventType, actorId: this._State.id, args: someArgs});
// };
/**
* @param {Actor} anActor
* @lends worldScripts.DayDiplomacy_000_Engine.$addActor
*/
this.$addActor = function (anActor) {
var state = this._State, responsesByType = state.responsesByType, initActions = state.initActions,
initActionsByType = state.initActionsByType, eventTypes = state.eventTypes, actorType = anActor.actorType,
id = anActor.id, responses = state.responses;
// We add the actor to the actors maps.
state.actorsByType[actorType].push(id);
state.actors[id] = anActor;
// We complete the existing actor responses with the engine responses.
var y = eventTypes.length;
while (y--) {
var eventType = eventTypes[y];
var eventTypeResponses = responsesByType[eventType] || (responsesByType[eventType] = {});
var responsesIdsToAdd = eventTypeResponses[actorType] || (eventTypeResponses[actorType] = []);
var x = responsesIdsToAdd.length;
while (x--) {
this.$addResponseToActor(responses[responsesIdsToAdd[x]], anActor);
}
}
// We execute the initActions on the actor
var initActionsToExecute = initActionsByType[actorType];
var z = initActionsToExecute.length;
while (z--) {
this.$letActorExecuteAction(anActor, initActions[initActionsToExecute[z]]);
}
};
// Consistent with history usage.
// this.disableActor = function (anActor) {
// var engineState = this._State, actorState = anActor._State, arr = engineState.actorsByType[actorState.actorType];
// arr.splice(arr.indexOf(actorState.id), 1);
// delete engineState.actors[actorState.id];
// };
/**
* @param {Actor} anActor
* @param {ActorType} thatObserverType
* @param {ActorId} thatObserverId
* @lends worldScripts.DayDiplomacy_000_Engine.$addObserverToActor
*/
this.$addObserverToActor = function (anActor, thatObserverType, thatObserverId) {
var observers = anActor.observers;
(observers[thatObserverType] || (observers[thatObserverType] = [])).push(thatObserverId);
};
/**
* @param {DiplomacyResponse}aResponse
* @param {Actor}anActor
*/
this.$addResponseToActor = function (aResponse, anActor) {
var responsesIdByEventType = anActor.responsesIdByEventType;
(responsesIdByEventType[aResponse.eventType] || (responsesIdByEventType[aResponse.eventType] = [])).push(aResponse.id);
};
// this.$Actor.prototype.removeResponse = function (aResponse) {
// var arr = this._State.responses[aResponse.eventType];
// arr.splice(arr.indexOf(aResponse.id), 1);
// };
/**
* @param {FunctionId} anId
* @param {function} aFunction
* @lends worldScripts.DayDiplomacy_000_Engine.$setFunction
*/
this.$setFunction = function (anId, aFunction) {
this._Functions[anId] = aFunction;
};
/**
* @param {Object} anObject
* @param {string} fieldName
* @param {Object} fieldValue
* @lends worldScripts.DayDiplomacy_000_Engine.$setField
*/
this.$setField = function (anObject, fieldName, fieldValue) {
if (anObject.hasOwnProperty("_State")) { // We put the field into _State
anObject._State[fieldName] = fieldValue;
} else {
anObject[fieldName] = fieldValue;
}
};
/**
* @param {Action} anInitAction
* @lends worldScripts.DayDiplomacy_000_Engine.$setInitAction
*/
this.$setInitAction = function (anInitAction) {
var initActions = this._State.initActions, initActionsByType = this._State.initActionsByType,
initActionActorType = anInitAction.actorType;
// We add the initAction to initActions and initActionsByType
initActions[anInitAction.id] = anInitAction;
(initActionsByType[initActionActorType] || (initActionsByType[initActionActorType] = [])).push(anInitAction.id);
// We execute the action on the existing actors in an ordered fashion.
this.$executeAction(anInitAction);
};
/**
* @param {Action} anAction
* @lends worldScripts.DayDiplomacy_000_Engine.$setRecurrentAction
*/
this.$setRecurrentAction = function (anAction) {
// We add the action to recurrentActions
var recurrentActionsByType = this._State.recurrentActionsByType, recurrentActions = this._State.recurrentActions,
actionEventType = anAction.eventType, actionActorType = anAction.actorType;
var eventTypeActions = recurrentActionsByType[actionEventType] || (recurrentActionsByType[actionEventType] = {});
(eventTypeActions[actionActorType] || (eventTypeActions[actionActorType] = [])).push(anAction.id);
recurrentActions[anAction.id] = anAction;
};
/**
* FIXME
* @param {string} name
* @param {*} defaultValue
* @returns {*}
* @lends worldScripts.DayDiplomacy_000_Engine.$initAndReturnSavedData
*/
this.$initAndReturnSavedData = function (name, defaultValue) {
return this._State[name] || (this._State[name] = defaultValue);
};
/**
* FIXME
* @param {string} name
* @param {*} defaultValue
* @returns {*}
* @lends worldScripts.DayDiplomacy_000_Engine.$initAndReturnSavedDataAndInitialize
*/
this.$initAndReturnSavedDataAndInitialize = function (name, defaultValue, initFunction) {
if (this._State[name] === undefined) {
initFunction();
}
return this._State[name] || (this._State[name] = defaultValue);
};
this.$executeAction = function (anAction) {
var ourActorIds = this._State.actorsByType[anAction.actorType], actors = this._State.actors;
var z = ourActorIds.length;
while (z--) {
this.$letActorExecuteAction(actors[ourActorIds[z]], anAction);
}
};
/**
* A Response contains a behaviour to be executed when a certain event happens.
* The responseFunction must take as first argument the responding actor,
* 2nd argument the eventActor, and may take as many additional arguments as you wish.
* The actorType is the type of the responding actors.
*
* @param {ResponseId}id
* @param {EventType}eventType
* @param {ActorType}actorType
* @param {FunctionId}responseFunctionId
* @return {DiplomacyResponse}
* @lends worldScripts.DayDiplomacy_000_Engine.$buildResponse
*/
this.$buildResponse = function (id, eventType, actorType, responseFunctionId) {
return {id: id, eventType: eventType, actorType: actorType, responseFunctionId: responseFunctionId};
};
/**
* @param {DiplomacyResponse} aResponse
* @lends worldScripts.DayDiplomacy_000_Engine.$setResponse
*/
this.$setResponse = function (aResponse) {
var state = this._State, actors = state.actors;
// We add the response to responses
state.responsesByType[aResponse.eventType][aResponse.actorType].push(aResponse.id);
state.responses[aResponse.id] = aResponse;
// We add the response to the existing actors in an ordered fashion.
var ourActorIds = state.actorsByType[aResponse.actorType];
var z = ourActorIds.length;
while (z--) {
this.$addResponseToActor(aResponse, actors[ourActorIds[z]]);
}
};
// this.unsetInitAction = function (anInitAction) { // This doesn't impact History.
// delete this._State.initActions[anInitAction.actorType][anInitAction.id];
// };
// this.unsetRecurrentAction = function (anAction) { // This doesn't impact History.
// var engineState = this._State, arr = engineState.recurrentActionsByType[anAction.eventType][anAction.actorType];
// arr.splice(arr.indexOf(anAction.id), 1);
// delete engineState.recurrentActions[anAction.id];
// };
// this.unsetResponse = function (aResponse) { // This doesn't impact History.
// var state = this._State, actors = state.actors;
// delete state.responses[aResponse.eventType][aResponse.actorType][aResponse.id];
// var ourActorIds = state.actorsByType[aResponse.actorType];
// var z = ourActorIds.length;
// while (z--) {
// actors[ourActorIds[z]].removeResponse(aResponse);
// }
// };
/**
* We don't allow to remove eventTypes as it would make the history inconsistent.
* @param {EventType} name - name of the new eventType, it must be different from already existing names.
* @param {int} position - the position in the ordered list of existing types
* @lends worldScripts.DayDiplomacy_000_Engine.$addEventType
*/
this.$addEventType = function (name, position) {
var state = this._State;
state.eventTypes.splice(position, 0, name);
if (this._debug) log("DiplomacyEngine", "Added " + name + " event type in position " + position + ". Current event types: " + state.eventTypes);
var ourResponses = (state.responsesByType[name] = {});
var ourRecurrentActions = (state.recurrentActionsByType[name] = {});
var actorTypes = state.actorTypes;
var z = actorTypes.length;
while (z--) {
var ourActorType = actorTypes[z];
ourResponses[ourActorType] = [];
ourRecurrentActions[ourActorType] = [];
}
state.eventsToPublish[name] = [];
state.eventsToPublishNextTurn[name] = [];
};
/**
* @param {ActorType} name
* @param {int} position
* @lends worldScripts.DayDiplomacy_000_Engine.$addActorType
*/
this.$addActorType = function (name, position) {
var state = this._State;
state.actorTypes.splice(position, 0, name);
state.actorsByType[name] = [];
state.initActionsByType[name] = [];
var responses = state.responsesByType;
var recurrentActions = state.recurrentActionsByType;
var z = state.eventTypes.length;
while (z--) {
var eventType = state.eventTypes[z];
responses[eventType][name] = [];
recurrentActions[eventType][name] = [];
}
};
/**
* Gives the next state. Returns empty string if array is finished.
* @param {EventType | ActorType} type
* @param currentState FIXME document what's a state
*/
this._nextState = function (type, currentState) {
var arr = this._State[type];
var newIndex = arr.indexOf(currentState) + 1;
return newIndex === arr.length ? "" : arr[newIndex];
};
/**
* @param {DiplomacyEvent} anEvent
* @private
*/
this._record = function (anEvent) {
var eventsToPublish = this._State.eventsToPublish, eventType = anEvent.eventType,
eventId = anEvent.id, eventActorId = anEvent.actorId, actorsEvents = this._State.actorsEvents;
// Stamping the event
anEvent.date = this._clock.seconds;
// Recording the history. This is ordered by insertion, so ordered by date.
(actorsEvents[eventActorId] || (actorsEvents[eventActorId] = [])).push(eventId);
this._State.events[eventId] = anEvent;
// Publishing the reality
(eventsToPublish[eventType] || (eventsToPublish[eventType] = [])).push(anEvent);
};
// this.recordForNextTurn = function (anEvent) {
// var eventsToPublishNextTurn = this._State.eventsToPublishNextTurn, eventType = anEvent.eventType;
// (eventsToPublishNextTurn[eventType] || (eventsToPublishNextTurn[eventType] = [])).push(anEvent);
// };
this._gatherEventsToPublish = function () {
var state = this._State;
var currentEventType = state.currentEventType, eventsToPublishNextTurn = state.eventsToPublishNextTurn;
var ourEvents = (eventsToPublishNextTurn[currentEventType] || (eventsToPublishNextTurn[currentEventType] = []));
// FIXME 0.perfectperf, when we use Events: does the length change? check through logs
// FIXME 0.perfectperf, when we use Events: 'while' could be cut into frames, but it would slow the history. Check the time spent through logs
while (ourEvents.length) {
var z = ourEvents.length;
while (z--) {
this._record(ourEvents.shift());
}
}
// We go to next eventType
var newEventType = this._nextState("eventTypes", currentEventType);
if (this._debug && newEventType !== "") log(this.name, "Gathering events to publish for state: " + newEventType);
state.currentEventType = newEventType || state.eventTypes[0];
state.currentActorType = state.actorTypes[0];
return !newEventType;
};
/** @return {boolean} - true when everything is finished, else false. */
this._populateStack = function () {
var state = this._State, currentEventType = state.currentEventType, currentActorType = state.currentActorType,
firstActorType = state.actorTypes[0];
if (!state.recurrentActionsIsDoneForCurrentEventType) {
if (this._debug) log(this.name, "Putting recurrent actions onto stack for event type: " + currentEventType + " and actor type: " + state.currentActorType);
this._putRecurrentActionsOntoStack(currentEventType, currentActorType);
// We go to next actorType
var newActorType = this._nextState("actorTypes", currentActorType);
state.currentActorType = newActorType || firstActorType;
state.recurrentActionsIsDoneForCurrentEventType = !newActorType;
return false; // No need to use too much time.
}
var ourEvents = state.eventsToPublish[currentEventType];
if (ourEvents.length) {
this._putEventOntoStack(ourEvents[0], currentActorType);
// We go to next actorType
var newActorType2 = this._nextState("actorTypes", currentActorType);
state.currentActorType = newActorType2 || firstActorType;
// When the event is processed, we remove it from the array.
newActorType2 || ourEvents.shift();
return false; // No need to use too much time.
}
// We go to next eventType
state.currentActorType = firstActorType;
state.recurrentActionsIsDoneForCurrentEventType = false;
var newEventType = this._nextState("eventTypes", currentEventType);
if (this._debug && newEventType !== "") log(this.name, "Gathering events to publish for state: " + newEventType);
// We may have finished: no more eventType, no more actorType, no more recurrentAction, no more event to respond to.
state.currentEventType = newEventType || state.eventTypes[0];
return !newEventType;
};
this._putRecurrentActionsOntoStack = function (currentEventType, currentActorType) {
var state = this._State, actions = state.recurrentActionsByType[currentEventType][currentActorType],
actorIds = state.actorsByType[currentActorType], shortStack = state.shortStack;
var y = actions.length;
while (y--) {
var id = actions[y];
var z = actorIds.length;
while (z--) {
shortStack.push({type: "action", actorId: actorIds[z], recurrentActionId: id});
}
}
};
this._putEventOntoStack = function (thatEvent, currentActorType) {
// FIXME perfectstyle we should use the eventId as arg rather than the whole event
var eventActorId = thatEvent.actorId, eventEventType = thatEvent.eventType, eventArgs = thatEvent.args,
state = this._State, actors = state.actors, eventActor = actors[eventActorId],
observers = eventActor.observers[currentActorType], z = observers.length, shortStack = state.shortStack,
responses = state.responses;
while (z--) {
var observer = actors[observers[z]];
// First argument: observer, 2nd arg: eventActor, other args: other args
var someArgs = [observer, eventActor].concat(eventArgs);
var responseIds = observer.responsesIdByEventType[eventEventType];
if (!responseIds) {
continue; // No responses to process for this observer
}
var y = responseIds.length;
while (y--) {
shortStack.push({
type: "response",
responseFunctionId: responses[responseIds[y]].responseFunctionId,
args: someArgs
});
}
}
};
/** @return {boolean} true if finished (empty stack), false otherwise. */
this._executeStack = function () {
var s = this._State;
var action = s.shortStack.shift();
if (action === undefined) {
return true;
}
// FIXME 0.perfectperf measure execution time?
if (action.type == "action") {
this._Functions[s.recurrentActions[action.recurrentActionId].actionFunctionId](s.actors[action.actorId]);
} else { // == "response"
this._Functions[action.responseFunctionId](action.args);
}
return false;
};
this._addFrameCallback = function () {
this._removeFrameCallback();
if (this._debug) log(this.name, "Adding frame callback");
this._State.callback = addFrameCallback(this._ourFrameCallback);
};
this._removeFrameCallback = function () {
if (this._State.callback) {
removeFrameCallback(this._State.callback);
delete this._State.callback;
if (this._debug) log(this.name, "Removed frame callback");
}
};
this._ourFrameCallback = function (delta) {
if (this._frame = ((this._frame || 0) + 1) % 10) { // One action each 10 frames
return; // Only one in n frames is used.
}
var state = this._State;
if (state.isJumpTokenBeingUsed) {
state.isJumpTokenBeingUsed = !(this._executeStack() && this._populateStack()); // Still some work to do ?
return; // we did enough this time
}
if (state.jumpTokenNb) { // Do we have an available jump token?
if (this._gatherEventsToPublish()) { // Finished gathering
if (this._debug) log(this.name, "Using a jump token");
state.jumpTokenNb--;
state.isJumpTokenBeingUsed = true;
}
return; // we did enough this time
}
if (this._debug) log(this.name, "No more jump token");
this._removeFrameCallback(); // We have finished, we remove the callback
}.bind(this);
/* ************************** Methods to save/restore *****************************************************/
this._functionReplacer = function (key, value) {
return typeof value == 'function' ? '/Function(' + value.toString() + ')/' : value;
};
this._functionReviver = (function () {
var innerFn = function innerFn(key, value) {
// All our special cases are strings // FIXME 0.perfectperf check if we get something else than string
if (typeof value != 'string') {
return value;
}
var that = innerFn; // Closure for recursion
if (value.match(that._functionRegexp)) { // FIXME 0.perfectperf: benchmark using only one regexp rather than 2
return eval(value.replace(that._functionReplaceRegexp, '($1)'));
}
return value;
};
innerFn._functionRegexp = new RegExp("^\\/Function\\([\\s\\S]*\\)\\/$");
innerFn._functionReplaceRegexp = new RegExp("^\\/Function\\(([\\s\\S]*)\\)\\/$");
return innerFn;
}.bind(this))();
/* ************************** Oolite events ***************************************************************/
this._startUp = function () {
var sa = this._missionVariables.DayDiplomacyEngine_EngineState;
if (sa && sa.length) { // Loading if necessary.
this._loadState(this._State, this._JSON.parse(sa));
this._loadState(this._Functions, this._JSON.parse(this._missionVariables.DayDiplomacyEngine_Functions, this._functionReviver));
}
delete this._startUp; // No need to startup twice
};
this.playerWillSaveGame = function (message) {
this._removeFrameCallback();
var start = new Date();
this._missionVariables.DayDiplomacyEngine_EngineState = this._JSON.stringify(this._State);
this._missionVariables.DayDiplomacyEngine_Functions = this._JSON.stringify(this._Functions, this._functionReplacer);
var end = new Date();
log("DiplomacyEngine", "Saved in ms: " + (end.getTime() - start.getTime()));
this._addFrameCallback();
};
this.shipExitedWitchspace = function () {
var s = this._State;
s.jumpTokenNb || (s.jumpTokenNb = 0);
s.jumpTokenNb++;
if (this._debug) log(this.name, "Added jump token");
};
this.shipDockedWithStation = function (station) {
this._addFrameCallback();
};
this.shipWillLaunchFromStation = function (station) {
this._removeFrameCallback();
};
/* ************************** Subscribing system for scripts order ****************************************/
this.startUp = function () {
worldScripts.DayDiplomacy_000_Engine.$subscribe(this.name);
delete this.startUp; // No need to startup twice
};
this.startUpComplete = function () {
var s = this._subscribers.sort();
var z = s.length, y = z - 1;
while (z--) {
var startDate = new Date();
worldScripts[s[y - z]]._startUp();
log(s[y - z], "startUp in ms: " + (new Date().getTime() - startDate.getTime()));
}
this.shipDockedWithStation(null); // When starting, the player is docked.
delete this.startUpComplete; // No need to startup twice
};
// FIXME create a type ScriptName?
/** names of scripts
* @type {string[]} */
this._subscribers = [];
/**
* Allows an external script to use the Diplomacy API.
* The external script must implement a function named _startUp() which will be called during the startUpComplete() function of the Diplomacy Engine.
* @name $subscribe
* @param {string} scriptName - the name property of the subscribing script object
* @lends worldScripts.DayDiplomacy_000_Engine.$subscribe
*/
this.$subscribe = function (scriptName) {
this._subscribers.push(scriptName);
}; |
Scripts/DayDiplomacy_GNNConnector.js |
"use strict";
this.name = "DayDiplomacy_015_GNN";
this.author = "David (Day) Pradier";
// noinspection JSUnusedGlobalSymbols Used by Oolite itself
this.copyright = "(C) 2017 David Pradier";
// noinspection JSUnusedGlobalSymbols Used by Oolite itself
this.licence = "CC-NC-by-SA 4.0";
this.description = "This script connects to the GNN OXP.";
/**
*
* @param message
* @lends worldScripts.DayDiplomacy_015_GNN.$publishNews
*/
this.$publishNews = function (message) {
var news = {ID:this.name, Message:message};
var returnCode = worldScripts.GNN._insertNews(news);
if (returnCode > 0) { // A prerequisite is wrong
log("DayDiplomacy_015_GNN.$publishNews", "GNN ERROR: " + returnCode);
} // else: everything is okay.
};
/**
*
* @param message
* @lends worldScripts.DayDiplomacy_015_GNN.$publishNewsNow
*/
this.$publishNewsNow = function (message) {
var news = {ID:this.name, Message:message};
var returnBoolean = worldScripts.GNN._showScreen(news);
if (!returnBoolean) { // A prerequisite is wrong
log("DayDiplomacy_015_GNN.$publishNewsNow", "GNN ERROR");
} // else: everything is okay.
};
/* ************************** Oolite events ***************************************************************/
this._startUp = function () {
delete this._startUp; // No need to startup twice
};
this.startUp = function () {
worldScripts.DayDiplomacy_000_Engine.$subscribe(this.name);
delete this.startUp; // No need to startup twice
}; |
Scripts/DayDiplomacy_History.js |
"use strict";
this.name = "DayDiplomacy_020_History";
this.author = "David (Day) Pradier";
// noinspection JSUnusedGlobalSymbols Used by Oolite itself
this.copyright = "(C) 2017 David Pradier";
// noinspection JSUnusedGlobalSymbols Used by Oolite itself
this.licence = "CC-NC-by-SA 4.0";
this.description = "This script displays an Interface showing the F7 system history.";
this._displayF4Interface = function () {
player.ship.hudHidden || (player.ship.hudHidden = true);
// for each event in history for this system, we add a line
var ourMessage = "", f = this._F, eff = this._eff,
ourEventsIds = this._Engine.$getActorEvents(this._selectedSystemActorId), events = this._Engine.$getEvents(),
y = ourEventsIds.length, _clock = clock;
while (y--) {
// Anti-chronological order
var thatEvent = events[ourEventsIds[y]];
ourMessage += _clock.clockStringForTime(thatEvent.date) + ": " + f[eff[thatEvent.eventType]](thatEvent)+"\n";
}
var opts = {
screenID: "DiplomacyHistoryScreenId",
title: "System history",
allowInterrupt: true,
exitScreen: "GUI_SCREEN_INTERFACES",
message: ourMessage
};
mission.runScreen(opts);
};
this._initF4Interface = function () {
player.ship.dockedStation.setInterface("DiplomacyHistory",
{
title: "System history",
category: "Diplomacy",
summary: "All the notable events in the system history",
callback: this._displayF4Interface.bind(this)
});
};
/* ************************** OXP public functions ********************************************************/
/**
* @param {EventType} eventType
* @param {function} func
* @lends worldScripts.DayDiplomacy_020_History.$setEventFormattingFunction
*/
this.$setEventFormattingFunction = function(eventType, func) {
var engine = this._Engine;
var fid = engine.$getNewFunctionId();
engine.$setFunction(fid, func);
this._eff[eventType] = fid;
};
/* ************************** Oolite events ***************************************************************/
this.infoSystemChanged = function (currentSystemId, previousSystemId) {
this._selectedSystemActorId = this._Systems.$getCurrentGalaxySystemsActorIdsBySystemsId()[currentSystemId];
};
this.shipDockedWithStation = function (station) {
this._initF4Interface();
};
this.missionScreenEnded = function () {
player.ship.hudHidden = false;
};
this._startUp = function () {
worldScripts.XenonUI && worldScripts.XenonUI.$addMissionScreenException("DiplomacyHistoryScreenId");
worldScripts.XenonReduxUI && worldScripts.XenonReduxUI.$addMissionScreenException("DiplomacyHistoryScreenId");
var engine = this._Engine = worldScripts.DayDiplomacy_000_Engine;
this._Systems = worldScripts.DayDiplomacy_010_Systems;
this._F = engine.$getFunctions();
this._selectedSystemActorId = this._Systems.$getCurrentGalaxySystemsActorIdsBySystemsId()[system.info.systemID]; // FIXME perfectperf?
this._eff = engine.$initAndReturnSavedData("eventFormatingFunctionsIds", {}); // { eventType => functionId }
this._initF4Interface();
delete this._startUp; // No need to startup twice
};
this.startUp = function () {
worldScripts.DayDiplomacy_000_Engine.$subscribe(this.name);
delete this.startUp; // No need to startup twice
}; |
Scripts/DayDiplomacy_JsDocDiplomacy.js |
"use strict";
/**
* An id identifying an {@link Actor}
* @typedef ActorId
* @alias {string}
*/
/**
* An id identifying an {@link Action}
* @typedef ActionId
* @alias {string}
*/
/**
* An id identifying a {@link DiplomacyEvent}
* @typedef EventId
* @alias {string}
*/
/**
* An id identifying a {@link DiplomacyFunction}
* @typedef FunctionId
* @alias {string}
*/
/**
* An id identifying a {@link DiplomacyResponse}
* @typedef ResponseId
* @alias {string}
*/
/**
* A type qualifying a {@link DiplomacyEvent}
* @typedef EventType
* @alias {string}
*/
/**
* A type of {@link Actor}
* @typedef ActorType
* @alias {string}
*/
/**
@typedef DiplomacyEvent
@property {EventId} id
@property {EventType} eventType
@property {ActorId} actorId - actorId
@property {Object[]} args
@property {number} date - seconds since epoch
*/
/**
@typedef DiplomacyResponse
@property {ResponseId} id
@property {EventType} eventType
@property {ActorType} actorType
@property {FunctionId} responseFunctionId
*/
/**
@typedef Actor
@property {ActorId} id
@property {ActorType} actorType
@property {Object<ActorType,ActorId[]>} observers
@property {Object<EventType,ResponseId[]>} responsesIdByEventType
*/
/**
@typedef Action
@property {ActionId} id
@property {EventType} eventType - anEventType is used to order the actions and events execution. For a same eventType, Actions are executed before Events.
@property {ActorType} actorType - Only actors of the type will execute the action.
@property {FunctionId} actionFunctionId - the id of a function which must take one and only one argument: the actor which will "act".
*/
/**
* @typedef Script
* @property {string} name FIXME
* @property {string} author FIXME
* @property {string} copyright FIXME
* @property {string} description FIXME
* @property {string} licence FIXME
*/
/**
* The Diplomacy Engine script
* @type Script
*/
worldScripts.DayDiplomacy_000_Engine;
/**
* The Diplomacy Planetary Systems script
* @type Script
*/
worldScripts.DayDiplomacy_010_Systems;
/**
* The Diplomacy-Snoopers connector script
* @type Script
*/
worldScripts.DayDiplomacy_015_GNN;
/**
* The Diplomacy History script
* @type Script
*/
worldScripts.DayDiplomacy_020_History;
/**
* The Diplomacy Economy script
* @type Script
*/
worldScripts.DayDiplomacy_030_EconomyEngine;
/**
* The Diplomacy War Engine script
* @type Script
*/
worldScripts.DayDiplomacy_040_WarEngine;
/**
* The Diplomacy War script
* @type Script
*/
worldScripts.DayDiplomacy_045_War;
/**
* The Diplomacy Citizenships script
* @type Script
*/
worldScripts.DayDiplomacy_060_Citizenships;
/**
* The XenonUI script, for compatibility
* @type Script
*/
worldScripts.XenonUI;
/**
* @function
*/
worldScripts.XenonUI.$addMissionScreenException;
/**
* The XenonReduxUI script, for compatibility
* @type Script
*/
worldScripts.XenonReduxUI;
/**
* @function
*/
worldScripts.XenonReduxUI.$addMissionScreenException; |
Scripts/DayDiplomacy_JsDocOolite.js |
"use strict";
/**
* The System class represents the current system. There is always one System object, available through the global property system.
* @typedef System
* @property {int} ID {@link http://wiki.alioth.net/index.php/Oolite_JavaScript_Reference:_System#ID}
* @property {SystemInfo} info {@link http://wiki.alioth.net/index.php/Oolite_JavaScript_Reference:_System#info}
* @property {function} infoForSystem {@link http://wiki.alioth.net/index.php/Oolite_JavaScript_Reference:_System#infoForSystem}
* @property {string} name {@link http://wiki.alioth.net/index.php/Oolite_JavaScript_Reference:_System#name}
* @property {int} population {@link http://wiki.alioth.net/index.php/Oolite_JavaScript_Reference:_System#population}
* @property {int} productivity {@link http://wiki.alioth.net/index.php/Oolite_JavaScript_Reference:_System#productivity}
* @property {int} techLevel {@link http://wiki.alioth.net/index.php/Oolite_JavaScript_Reference:_System#techLevel}
* @property {int} government {@link http://wiki.alioth.net/index.php/Oolite_JavaScript_Reference:_System#government}
* @see {@link http://wiki.alioth.net/index.php/Oolite_JavaScript_Reference:_System}
*/
/**
* @function System.infoForSystem
* @param {int} galaxyID
* @param {int} systemID
* @return {SystemInfo}
* @see {@link http://wiki.alioth.net/index.php/Oolite_JavaScript_Reference:_System#infoForSystem}
*/
/**
* SystemInfo objects provide information about a specific system.
* @typedef SystemInfo
* @property {int} galaxyID {@link http://wiki.alioth.net/index.php/Oolite_JavaScript_Reference:_SystemInfo#galaxyID}
* @property {int} systemID {@link http://wiki.alioth.net/index.php/Oolite_JavaScript_Reference:_SystemInfo#systemID}
* @property {int} productivity
* @property {int} population
* @property {function} systemsInRange {@link http://wiki.alioth.net/index.php/Oolite_JavaScript_Reference:_SystemInfo#systemsInRange}
* @see {@link http://wiki.alioth.net/index.php/Oolite_JavaScript_Reference:_SystemInfo}
*/
/**
* Returns the SystemInfo of the systems nearer than 7 ly from the original SystemInfo
* @function SystemInfo.systemsInRange
* @instance
* @return {SystemInfo[]}
*/
// Note: player.ship doesn't exist in the Oolite player wiki.
/**
* The Player class is represents the player. There is always exactly one Player object in existence, which can be accessed through the player global property.
* @typedef Player
* @property {PlayerShip} ship
* @property {Number} credits {@link http://wiki.alioth.net/index.php/Oolite_JavaScript_Reference:_Player#credits}
* @property {int} bounty {@link http://wiki.alioth.net/index.php/Oolite_JavaScript_Reference:_Player#bounty}
* @property {function} commsMessage {@link http://wiki.alioth.net/index.php/Oolite_JavaScript_Reference:_Player#commsMessage}
* @see {@link http://wiki.alioth.net/index.php/Oolite_JavaScript_Reference:_Player}
*/
/**
* @function Player.commsMessage
* @param {string} message
* @instance
* @see {@link http://wiki.alioth.net/index.php/Oolite_JavaScript_Reference:_Player#commsMessage}
*/
/**
* The Ship class is an Entity representing a ship, station, missile, cargo pod or other flying item – anything that can be specified in shipdata.plist.
* @typedef Ship
* @property {int} homeSystem {@link http://wiki.alioth.net/index.php/Oolite_JavaScript_Reference:_Ship#homeSystem}
* @property {function} awardEquipment {@link http://wiki.alioth.net/index.php/Oolite_JavaScript_Reference:_Ship#awardEquipment}
* @see {@link http://wiki.alioth.net/index.php/Oolite_JavaScript_Reference:_Ship}
*/
/**
* The Station class is an Entity representing a station or carrier (i.e., a ship with a docking port). A Station has all the properties and methods of a Ship, and some others.
* @typedef {Ship} Station
* @augments {Ship}
* @property {function} setInterface {@link http://wiki.alioth.net/index.php/Oolite_JavaScript_Reference:_Station#setInterface}
* @see {@link http://wiki.alioth.net/index.php/Oolite_JavaScript_Reference:_Station}
*/
/**
* The PlayerShip class is an Entity representing the player’s ship. The PlayerShip has all the properties and methods of a Ship, and several others. There is always exactly one PlayerShip object in existence, which can be accessed through player.ship.
* @typedef {Ship} PlayerShip
* @augments {Ship}
* @property {boolean} hudHidden {@link http://wiki.alioth.net/index.php/Oolite_JavaScript_Reference:_PlayerShip#hudHidden}
* @property {Station} dockedStation {@link http://wiki.alioth.net/index.php/Oolite_JavaScript_Reference:_PlayerShip#dockedStation}
* @see {@link http://wiki.alioth.net/index.php/Oolite_JavaScript_Reference:_PlayerShip}
*/
/**
* The mission global object is used to run mission screens, and perform other actions related to mission scripting.
* @typedef Mission
* @property {function} runScreen {@link http://wiki.alioth.net/index.php/Oolite_JavaScript_Reference:_Mission#runScreen}
* @see {@link http://wiki.alioth.net/index.php/Oolite_JavaScript_Reference:_Mission}
*/
/**
* The clock global object is used to tell the time. Apart from absoluteSeconds, all its properties have to do with game clock time.
* @typedef Clock
* @property {int} seconds
* @property {function} clockStringForTime {@link http://wiki.alioth.net/index.php/Oolite_JavaScript_Reference:Clock#clockStringForTime}
* @see {@link http://wiki.alioth.net/index.php/Oolite_JavaScript_Reference:_Clock}
*/
/**
* @name system
* @type System
* */
var system;
/**
* @name player
* @type Player
* */
var player;
/**
* @name mission
* @type Mission
* */
var mission;
/**
* @name missionVariables
* @type Object
* @see {@link http://wiki.alioth.net/index.php/Oolite_JavaScript_Reference:_Global#missionVariables}
* */
var missionVariables;
/**
* The dictionary of all available oxp scripts.
* @name worldScripts
* @type Object
* @see {@link http://wiki.alioth.net/index.php/Oolite_JavaScript_Reference:_Global#worldScripts}
*/
var worldScripts;
/**
* @name clock
* @type Clock
* */
var clock;
/**
* @function
* @param {string} prefix prefix
* @param {string} textToLog text to log
* @see {@link http://wiki.alioth.net/index.php/Oolite_JavaScript_Reference:_Global#log}
*/
var log = function(prefix, textToLog) {};
/**
* @function
* @param {function} callback
* @return {string} trackingID
* @see {@link http://wiki.alioth.net/index.php/Oolite_JavaScript_Reference:_Global#addFrameCallback}
*/
var addFrameCallback = function (callback) {};
/**
* @function
* @param {string} trackingID
* @see {@link http://wiki.alioth.net/index.php/Oolite_JavaScript_Reference:_Global#removeFrameCallback}
*/
var removeFrameCallback = function (trackingID) {}; |
Scripts/DayDiplomacy_Systems.js |
"use strict";
this.name = "DayDiplomacy_010_Systems";
this.author = "David (Day) Pradier";
// noinspection JSUnusedGlobalSymbols Used by Oolite itself
this.copyright = "(C) 2017 David Pradier";
// noinspection JSUnusedGlobalSymbols Used by Oolite itself
this.licence = "CC-NC-by-SA 4.0";
this.description = "This script creates systems.";
/**
* An object identifying a planetary system
* @typedef {Object} PlanetarySystem
* @property {int} galaxyID - the galaxyID of the system
* @property {int} systemID - the systemID of the system
* @property {string} name - the name of the system
*/
/* ************************** OXP public functions *******************************************************/
/**
*
* @param {int} galaxyID - Identifies the galaxy of the wanted system
* @param {int} systemID - Identifies the wanted system in the given galaxy
* @return {Actor}
* @lends worldScripts.DayDiplomacy_010_Systems.$retrieveActorFromSystem
*/
this.$retrieveActorFromSystem = function (galaxyID, systemID) {
return this._Engine.$getActors()[this._systemsByGalaxyAndSystemId[galaxyID][systemID]];
};
/**
* @param {int} galaxyID - Identifies the galaxy of the wanted system
* @param {int} systemID - Identifies the wanted system in the given galaxy
* @returns {String} - Returns the system name of the system defined by the given galaxyId and systemId
* @lends worldScripts.DayDiplomacy_010_Systems.$retrieveNameFromSystem
*/
this.$retrieveNameFromSystem = function (galaxyID, systemID) {
return this._Engine.$getActors()[this._systemsByGalaxyAndSystemId[galaxyID][systemID]].name;
};
/**
* @returns {Object} a dictionary with {int} galaxyId key and as value: a dictionary with {int} systemId key and as value: the corresponding {@link ActorId}
* @lends worldScripts.DayDiplomacy_010_Systems.$getSystemsActorIdsByGalaxyAndSystemId
*/
this.$getSystemsActorIdsByGalaxyAndSystemId = function() {
return this._systemsByGalaxyAndSystemId;
};
/**
* For the current galaxy only
* @returns {Object} a dictionary with {int} systemId key and as value: the corresponding {@link ActorId}
* @lends worldScripts.DayDiplomacy_010_Systems.$getCurrentGalaxySystemsActorIdsBySystemsId
*/
this.$getCurrentGalaxySystemsActorIdsBySystemsId = function() {
return this._systemsByGalaxyAndSystemId[system.info.galaxyID];
};
/* ************************** OXP private functions *******************************************************/
/**
* @param {int} aGalaxyNb
* @private
*/
this._setObservers = function (aGalaxyNb) {
// We set the observers. No need to use an initAction as there won't be any more system.
var engine = this._Engine;
var actorsIdByType = engine.$getActorsIdByType("SYSTEM");
var actors = engine.$getActors();
var galaxyFirstSystem = actors[actorsIdByType[255 + 256 * (7 - aGalaxyNb)]];
var galaxyLastSystem = actors[actorsIdByType[256 * (7 - aGalaxyNb)]];
var firstSystemKnownObservers = engine.$getObservers(galaxyFirstSystem, "SYSTEM");
var lastSystemKnownObservers = engine.$getObservers(galaxyLastSystem, "SYSTEM");
if (firstSystemKnownObservers && firstSystemKnownObservers.length && lastSystemKnownObservers.length) {
return; // Already initialized
}
var infoForSystem = System.infoForSystem, sys = this._systemsByGalaxyAndSystemId, z = actorsIdByType.length;
while (z--) {
var thisActor = actors[actorsIdByType[z]];
if (thisActor.galaxyNb === aGalaxyNb && !thisActor.observers.length) {
var observers = infoForSystem(aGalaxyNb, thisActor.systemId).systemsInRange();
var y = observers.length;
while (y--) {
var observer = observers[y];
engine.$addObserverToActor(thisActor, "SYSTEM", sys[observer.galaxyID][observer.systemID]);
}
}
}
};
this._startUp = function () {
var engine = this._Engine = worldScripts.DayDiplomacy_000_Engine;
var sys = this._systemsByGalaxyAndSystemId = engine.$initAndReturnSavedData("systemsByGalaxyAndSystemId", {});
// Not initializing if already done.
if (engine.$getActorTypes().indexOf("SYSTEM") !== -1) {
return;
}
/** @type {ActorType} */
var actorType = "SYSTEM";
engine.$addActorType(actorType, 0);
// We initiate the systems
var i = 8;
while (i--) {
var j = 256;
while (j--) {
var id = engine.$getNewActorId();
var aSystem = engine.$buildActor("SYSTEM", id);
// FIXME why do I use setField here rather than directly 'aSystem.galaxyNb = '?
engine.$setField(aSystem, "galaxyNb", i);
engine.$setField(aSystem, "systemId", j);
engine.$addActor(aSystem);
(sys[i] || (sys[i] = {}))[j] = id; // Needed for quick access in the next part.
}
}
// We init the observers for the current galaxy
this._setObservers(system.info.galaxyID);
delete this._startUp; // No need to startup twice
};
/* ************************** Oolite events ***************************************************************/
// noinspection JSUnusedGlobalSymbols - Called by Oolite itself
this.playerEnteredNewGalaxy = function (galaxyNumber) {
// This function is necessary as we can't calculate distances in other galaxies.
this._setObservers(galaxyNumber);
};
this.startUp = function () {
worldScripts.DayDiplomacy_000_Engine.$subscribe(this.name);
delete this.startUp; // No need to startup twice
}; |
Scripts/DayDiplomacy_War.js |
"use strict";
this.name = "DayDiplomacy_045_War";
this.author = "David (Day) Pradier";
// noinspection JSUnusedGlobalSymbols Used by Oolite itself
this.copyright = "(C) 2017 David Pradier";
// noinspection JSUnusedGlobalSymbols Used by Oolite itself
this.licence = "CC-NC-by-SA 4.0";
this.description = "This script makes systems ally to each other," +
" break their alliances," +
" make peace and war," +
" publish the related news," +
" draw the war and the diplomatic maps.";
/* ************************** OXP private functions *******************************************************/
// FIXME 0.14 make that as long as we are not in 1.0, the diplomacy save data is erased when there is a new version?
this._initSystemsScores = function (aGalaxyNb) {
// Initializing static scores
// For a given galaxy, for each system in the galaxy, for each system it observes,
// it must assign a score to some properties, then recalculate the final score.
// FIXME perfectstyle shouldn't this script be actorType-agnostic?
var engine = this._s;
var actorsIdByType = engine.$getActorsIdByType("SYSTEM");
var actors = engine.$getActors();
var z = actorsIdByType.length;
var we = this._we;
while (z--) {
var thisActor = actors[actorsIdByType[z]];
if (thisActor.galaxyNb != aGalaxyNb) {
continue;
}
var observersId = thisActor.observers["SYSTEM"];
var y = observersId.length;
while (y--) {
we.$recalculateScores(actors[observersId[y]], thisActor);
}
}
};
this._drawDiplomaticMap = function () {
var scores = this._we.$getScores();
var actors = this._s.$getActors();
var links = [];
for (var observedId in scores) {
if (scores.hasOwnProperty(observedId)) {
var observed = actors[observedId];
var observedNb = observed.systemId;
var galaxyNb = observed.galaxyNb;
var observedScores = scores[observedId];
for (var observerId in observedScores) {
if (observedScores.hasOwnProperty(observerId)) {
var observerNb = actors[observerId].systemId;
// Doc: "When setting link_color, the lower system ID must be placed first,
// because of how the chart is drawn."
if (observerNb < observedNb) {
var scoreFromTo = observedScores[observerId].SCORE;
var scoreToFrom = scores[observerId][observedId].SCORE;
if (scoreFromTo || scoreToFrom) {
var color = null;
if (scoreFromTo > 0 && scoreToFrom > 0) {
color = "greenColor";
} else if (scoreFromTo < 0 && scoreToFrom < 0) {
color = "redColor";
} else if (scoreFromTo * scoreToFrom < 0) {
color = "yellowColor";
} else if (scoreFromTo + scoreToFrom > 0) {
color = "blueColor";
} else {
color = "orangeColor";
}
links.push({galaxyNb: galaxyNb, from: observerNb, to: observedNb, color: color});
}
}
}
}
}
}
this._drawMap(links);
};
this._drawWarMap = function () {
var alliancesAndWars = this._we.$getAlliancesAndWars();
var actors = this._s.$getActors();
var links = [];
for (var actorId in alliancesAndWars) {
if (alliancesAndWars.hasOwnProperty(actorId)) {
var actorAlliancesAndWars = alliancesAndWars[actorId];
var actor = actors[actorId];
var systemNb = actor.systemId;
var galaxyNb = actor.galaxyNb;
for (var targetId in actorAlliancesAndWars) {
if (actorAlliancesAndWars.hasOwnProperty(targetId)) {
var targetSystemNb = actors[targetId].systemId;
// Doc: "When setting link_color, the lower system ID must be placed first,
// because of how the chart is drawn."
if (systemNb < targetSystemNb) {
links.push({
galaxyNb: galaxyNb,
from: systemNb,
to: targetSystemNb,
color: actorAlliancesAndWars[targetId] === 1 ? "greenColor" : "redColor"
});
}
}
}
}
}
this._drawMap(links);
};
this._drawWarringMap = function () {
var alliancesAndWars = this._we.$getAlliancesAndWars();
var actors = this._s.$getActors();
var warringSystems = [];
var m = mission;
mainloop:
for (var actorId in alliancesAndWars) {
if (alliancesAndWars.hasOwnProperty(actorId)) {
var actorAlliancesAndWars = alliancesAndWars[actorId];
var systemNb = actors[actorId].systemId;
for (var targetId in actorAlliancesAndWars) {
if (actorAlliancesAndWars.hasOwnProperty(targetId)) {
if (actorAlliancesAndWars[targetId] === -1) {
m.markSystem({system: systemNb, name: "DayDiplomacyWarringMap"});
continue mainloop;
}
}
}
}
}
};
this._drawMap = function (links) {
var systemInfo = SystemInfo;
var z = links.length;
while (z--) {
var link = links[z];
// Hmm... We calculate and then set the links for all the galaxies...
// This is useless, but at the same time simpler and maybe useful for the future.
systemInfo.setInterstellarProperty(link.galaxyNb, link.from, link.to, 2, "link_color", link.color);
}
this._links = links;
};
this._resetLinks = function () {
var m = mission;
var i = 256;
while(i--) {
m.unmarkSystem({system:i, name:"DayDiplomacyWarringMap"});
}
var links = this._links;
if (!links) return;
var systemInfo = SystemInfo;
var z = links.length;
while (z--) {
var link = links[z];
systemInfo.setInterstellarProperty(link.galaxyNb, link.from, link.to, 2, "link_color", null);
}
this._links = null;
};
/**
* If 2 vectors are given, center is the center of the vectors, and zoom the customChartCentreInLY value
* necessary to display the 2 vectors and 20% more space around.
* If only one vector is given, the center is that vector, and the zoom is 1.5.
* @param {Vector3D} v1
* @param {Vector3D} v2
* @return {{center: Vector3D, zoom: number}}
* @private
*/
this._getCustomZoom = function (v1, v2) {
var z = 1.5;
if (v2) {
var v = v1.subtract(v2).multiply(1.2);
z = Math.min(4, Math.max(1, Math.abs(v.x) / 100 * 4, Math.abs(v.y) / 50 * 4));
}
return {
center: v2 ? v1.add(v2).multiply(.5) : v1,
zoom: z
};
};
/**
* Save the target system and make as if there was no target system, so that no trajectory appears.
* @private
*/
this._saveTarget = function () {
if (!this._savedTarget) {
this._savedTarget = player.ship.targetSystem;
player.ship.targetSystem = system.info.systemID;
}
};
/**
* Reloads the target system.
* @private
*/
this._loadTarget = function () {
if (this._savedTarget) {
player.ship.targetSystem = this._savedTarget;
this._savedTarget = undefined;
}
};
this._F4InterfaceCallback = function (choice) {
this._loadTarget();
// Exit
if (choice === "5_EXIT") {
return;
}
// Default choice
choice = choice === "DiplomacyWars" ? "0_WARRING_NO_TRAVEL" : choice;
// Choice
var choices = choice.split("_");
var no = choices[0], wd = choices[1], qs = choices[2], td = choices[3];
// Options
var wdp = ["WARRING", "WARS", "DIPLOMACY"];
var qsp = ["QUICK", "SHORT", "NO"];
var tdp = ["TARGET", "TRAVEL"];
// Next proposed options
var nextwd = wdp[(wdp.indexOf(wd) + 1) % wdp.length];
var nextqs = qsp[(qsp.indexOf(qs) + 1) % qsp.length];
var nexttd = tdp[(tdp.indexOf(td) + 1) % tdp.length];
var bgs = {QUICK: "CUSTOM_CHART_QUICKEST", SHORT: "CUSTOM_CHART_SHORTEST", NO: "CUSTOM_CHART"};
var texts = {
WARRING: "Display warring systems",
WARS: "Display wars map",
DIPLOMACY: "Display diplomacy map",
QUICK: "Display quickest travel",
SHORT: "Display shortest travel",
NO: "Display no travel",
TARGET: "Display the target surroundings",
TRAVEL: "Display the travel surroundings"
};
// Init
this._resetLinks();
var playerShip = player.ship;
playerShip.hudHidden || (playerShip.hudHidden = true);
var opts = {
screenID: "DiplomacyWarScreenId",
title: "Star wars",
allowInterrupt: true,
exitScreen: "GUI_SCREEN_INTERFACES",
choices: {"5_EXIT": "Exit"}
};
if (no === '4'/*Help*/) {
opts.message = "Warring systems map: red crosses\n\nDiplomacy map:\nGreen: Love\nBlue: Love+Neutrality\nGray: Neutrality\nYellow: Love+Hate\nOrange: Neutrality+Hate\nRed: Hate\n"
+ "\n\nWars map:\nRed: war\nGreen: alliance";
} else {
// Screen
var info = system.info;
var currentSystemCoordinates = info.coordinates;
var targetCoordinates = System.infoForSystem(info.galaxyID, playerShip.targetSystem).coordinates;
var customZoom = td === "TARGET"
? this._getCustomZoom(targetCoordinates, undefined)
: this._getCustomZoom(currentSystemCoordinates, targetCoordinates);
if (qs === "NO") this._saveTarget();
opts.backgroundSpecial = bgs[qs];
opts.customChartZoom = customZoom.zoom;
opts.customChartCentreInLY = customZoom.center;
switch (wd) {
case 'DIPLOMACY':
this._drawDiplomaticMap();
break;
case 'WARS':
this._drawWarMap();
break;
case 'WARRING':
this._drawWarringMap();
break;
}
}
opts.choices[[1, nextwd, qs, td].join('_')] = texts[nextwd];
opts.choices[[2, wd, nextqs, td].join('_')] = texts[nextqs];
opts.choices[[3, wd, qs, nexttd].join('_')] = texts[nexttd];
opts.choices[[4, wd, qs, td].join('_')] = "Help";
mission.runScreen(opts, this._F4InterfaceCallback.bind(this));
};
this._initF4Interface = function () {
if (player.ship.hasEquipmentProviding("EQ_ADVANCED_NAVIGATIONAL_ARRAY")) {
player.ship.dockedStation.setInterface("DiplomacyWars",
{
title: "Star wars",
category: "Diplomacy",
summary: "Wars and diplomacy",
callback: this._F4InterfaceCallback.bind(this)
});
}
};
this._startUp = function () {
// FIXME 0.perfectstyle hmff, this might have to be into its own function.
// Nope, it would be contrary to perfectperf. Explain that in TechnicalPrinciples.txt.
// XenonUI would overlay over our mission screens without these exception.
// FIXME 0.perfectstyle i should have a list of screens, rather than copying here their names, to avoid forgetting
// to update here when I add or change a screen.
worldScripts.XenonUI && worldScripts.XenonUI.$addMissionScreenException("DiplomacyWarScreenId");
worldScripts.XenonReduxUI && worldScripts.XenonReduxUI.$addMissionScreenException("DiplomacyWarScreenId");
this._storedNews = []; // No real need to save it
var engine = this._s;
var we = this._we = worldScripts.DayDiplomacy_040_WarEngine;
var sf = we.$getScoringFunctions();
// Economy comparison
if (sf.indexOf("EconomyComparison") === -1) {
we.$addScoringFunction("EconomyComparison", function (observer, observed) {
var map = {
0: {0: +0.5, 1: -1.0, 2: -0.5, 3: -1.0, 4: -1.0, 5: -0.5, 6: -0.5, 7: -0.5}, // Anarchy
1: {0: +0.0, 1: +0.5, 2: -0.5, 3: -0.5, 4: -1.0, 5: -0.5, 6: -1.0, 7: -0.5}, // Feudal
2: {0: +0.0, 1: +0.0, 2: +0.5, 3: -0.5, 4: -0.5, 5: +0.5, 6: +0.0, 7: +0.0}, // Multi-government
3: {0: +0.0, 1: +0.0, 2: +0.0, 3: +0.5, 4: +0.0, 5: +0.0, 6: -0.5, 7: +0.0}, // Dictator
4: {0: -0.5, 1: -0.5, 2: +0.0, 3: +0.0, 4: +0.5, 5: +0.0, 6: -0.5, 7: -0.5}, // Communist
5: {0: +0.0, 1: +0.0, 2: +0.5, 3: -0.5, 4: +0.0, 5: +0.5, 6: +0.0, 7: +0.0}, // Confederacy
6: {0: +0.0, 1: -0.5, 2: +0.0, 3: -0.5, 4: -0.5, 5: +0.0, 6: +0.5, 7: +0.0}, // Democracy
7: {0: +0.0, 1: +0.0, 2: +0.0, 3: +0.0, 4: -1.0, 5: +0.0, 6: +0.0, 7: +0.5} // Corporate
};
return map[observer.government][observed.government];
}, 0);
}
// Alliances influence on score, this function is and should be last executed.
if (sf.indexOf("alliancesAndWarsInfluence") === -1) {
we.$addScoringFunction("alliancesAndWarsInfluence", function alliancesAndWarsInfluence(observer, observed) {
/* This function calculates the relation bonus given by observer to observed, depending on observed allies and foes.
* If their allies are considered nice by observer, they get a bonus.
* If their foes are considered baddies by observer, they get a bonus.
* And vice-versa. */
var that = alliancesAndWarsInfluence;
var we = that.we || (that.we = worldScripts.DayDiplomacy_040_WarEngine);
var observedAlliesAndFoes = we.$getAlliancesAndWars()[observed.id];
var allScores = we.$getScores();
var observerId = observer.id;
var result = 0;
for (var alliedId in observedAlliesAndFoes) {
if (observedAlliesAndFoes.hasOwnProperty(alliedId)) {
var scores = allScores[alliedId][observerId];
scores && (result += observedAlliesAndFoes[alliedId] * scores.SCORE);
}
}
return result > 0 ? .25 : result < 0 ? -.25 : 0;
}, 1);
}
this._initSystemsScores(system.info.galaxyID);
// We set the response to the ALLY event.
var allyResponseFunctionId = "diplomacyAlliancesOnSystemAllyFunction";
if (!engine.$getFunctions()[allyResponseFunctionId]) {
// We use a recurrent action to recalculate the scores,
// as doing it on every event would generate LOTS of calculus.
// Currently, we only generate the news.
var diplomacyAlliancesOnSystemAllyFunction = function diplomacyAlliancesOnSystemAllyFunction(argsArray) {
/** @type {Actor} */
var respondingActor = argsArray[0];
/** @type {Actor} */
var eventActor = argsArray[1];
/** @type {ActorId} */
var alliedActorId = argsArray[2];
// On ALLY event, if the player is in a responder system, a news is generated.
// This could be optimized, but the role of this function should be to manage all responses.
if (system.info.name === respondingActor.name) {
var allyName = worldScripts.DayDiplomacy_000_Engine.$getActors()[alliedActorId].name;
if (respondingActor.name === allyName) {
worldScripts.DayDiplomacy_015_GNN.$publishNews("YOU might be interested in knowing that " + eventActor.name + " just allied with " + allyName
+ ".\n\nAs Commander Diziet Sma, currently aboard the \"Blackwidow\" Pitviper S.E., famously said, 'the neatest definition of diplomacy I've seen is \"The art of saying 'nice doggy' while you reach behind you for a rock to throw.\"'.\n\nSo with that in mind, Who will gain? Who will lose?\n\nTruth is, we don't know!");
}
}
};
engine.$setFunction(allyResponseFunctionId, diplomacyAlliancesOnSystemAllyFunction);
engine.$setResponse(engine.$buildResponse(engine.$getNewResponseId(), "ALLY", "SYSTEM", allyResponseFunctionId));
}
// We set the response to the BREAK event.
var breakResponseFunctionId = "diplomacyAlliancesOnSystemBreakFunction";
if (!engine.$getFunctions()[breakResponseFunctionId]) {
// We use a recurrent action to recalculate the scores,
// as doing it on every event would generate LOTS of calculus.
// Currently, we only generate the news.
var diplomacyAlliancesOnSystemBreakFunction = function diplomacyAlliancesOnSystemBreakFunction(argsArray) {
/** @type {Actor} */
var respondingActor = argsArray[0];
/** @type {Actor} */
var eventActor = argsArray[1];
/** @type {ActorId} */
var alliedActorId = argsArray[2];
// On BREAK event, if the player is in a responder system, a news is generated.
if (system.info.name === respondingActor.name) {
var allyName = worldScripts.DayDiplomacy_000_Engine.$getActors()[alliedActorId].name;
if (respondingActor.name === allyName) {
worldScripts.DayDiplomacy_015_GNN.$publishNews("YOU might be interested in knowing that " + eventActor.name + " just broke their alliance with " + allyName
+ ".\n\nAs Commander Diziet Sma, currently aboard the \"Blackwidow\" Pitviper S.E., famously said, 'the neatest definition of diplomacy I've seen is \"The art of saying 'nice doggy' while you reach behind you for a rock to throw.\"'.\n\nSo with that in mind, Who will gain? Who will lose?\n\nTruth is, we don't know!");
}
}
};
engine.$setFunction(breakResponseFunctionId, diplomacyAlliancesOnSystemBreakFunction);
engine.$setResponse(engine.$buildResponse(engine.$getNewResponseId(), "BREAK", "SYSTEM", breakResponseFunctionId));
}
// We set the response to the WAR event.
var warResponseFunctionId = "diplomacyAlliancesOnSystemWarFunction";
if (!engine.$getFunctions()[warResponseFunctionId]) {
// We use a recurrent action to recalculate the scores,
// as doing it on every event would generate LOTS of calculus.
// Currently, we only generate the news.
var diplomacyAlliancesOnSystemWarFunction = function diplomacyAlliancesOnSystemWarFunction(argsArray) {
/** @type {Actor} */
var respondingActor = argsArray[0];
/** @type {Actor} */
var eventActor = argsArray[1];
/** @type {ActorId} */
var foeActorId = argsArray[2];
// On WAR event, if the player is in a responder system, a news is generated.
if (system.info.name === respondingActor.name) {
var foeName = worldScripts.DayDiplomacy_000_Engine.$getActors()[foeActorId].name;
if (respondingActor.name === foeName) {
// FIXME 0.14 make different citation for war and peace
worldScripts.DayDiplomacy_015_GNN.$publishNews("YOU might be interested in knowing that " + eventActor.name + " just declared war with " + foeName
+ ".\n\nAs Commander Diziet Sma, currently aboard the \"Blackwidow\" Pitviper S.E., famously said, 'the neatest definition of diplomacy I've seen is \"The art of saying 'nice doggy' while you reach behind you for a rock to throw.\"'.\n\nSo with that in mind, Who will gain? Who will lose?\n\nTruth is, we don't know!");
}
}
};
engine.$setFunction(warResponseFunctionId, diplomacyAlliancesOnSystemWarFunction);
engine.$setResponse(engine.$buildResponse(engine.$getNewResponseId(), "WAR", "SYSTEM", warResponseFunctionId));
}
// We set the response to the PEACE event.
var peaceResponseFunctionId = "diplomacyAlliancesOnSystemPeaceFunction";
if (!engine.$getFunctions()[peaceResponseFunctionId]) {
// We use a recurrent action to recalculate the scores,
// as doing it on every event would generate LOTS of calculus.
// Currently, we only generate the news.
var diplomacyAlliancesOnSystemPeaceFunction = function diplomacyAlliancesOnSystemPeaceFunction(argsArray) {
/** @type {Actor} */
var respondingActor = argsArray[0];
/** @type {Actor} */
var eventActor = argsArray[1];
/** @type {ActorId} */
var foeActorId = argsArray[2];
// On PEACE event, if the player is in a responder system, a news is generated.
if (system.info.name === respondingActor.name) {
var foeName = worldScripts.DayDiplomacy_000_Engine.$getActors()[foeActorId].name;
if (respondingActor.name === foeName) {
// FIXME 0.14 make different citation for war and peace
worldScripts.DayDiplomacy_015_GNN.$publishNews("YOU might be interested in knowing that " + eventActor.name + " just made peace with " + foeName
+ ".\n\nAs Commander Diziet Sma, currently aboard the \"Blackwidow\" Pitviper S.E., famously said, 'the neatest definition of diplomacy I've seen is \"The art of saying 'nice doggy' while you reach behind you for a rock to throw.\"'.\n\nSo with that in mind, Who will gain? Who will lose?\n\nTruth is, we don't know!");
}
}
};
engine.$setFunction(peaceResponseFunctionId, diplomacyAlliancesOnSystemPeaceFunction);
engine.$setResponse(engine.$buildResponse(engine.$getNewResponseId(), "PEACE", "SYSTEM", peaceResponseFunctionId));
}
this._initF4Interface();
delete this._startUp; // No need to startup twice
};
/* ************************** Oolite events ***************************************************************/
this.startUp = function () {
this._s = worldScripts.DayDiplomacy_000_Engine;
this._s.$subscribe(this.name);
delete this.startUp; // No need to startup twice
};
this.shipDockedWithStation = function (station) {
this._initF4Interface();
};
this.equipmentAdded = function (equipmentKey) {
if (equipmentKey === "EQ_ADVANCED_NAVIGATIONAL_ARRAY") {
this._initF4Interface();
}
};
this.playerEnteredNewGalaxy = function (galaxyNumber) {
this._initSystemsScores(galaxyNumber);
};
this.missionScreenEnded = function () {
player.ship.hudHidden = false;
this._resetLinks();
this._loadTarget();
}; |
Scripts/DayDiplomacy_WarEngine.js |
"use strict";
this.name = "DayDiplomacy_040_WarEngine";
this.author = "David (Day) Pradier";
// noinspection JSUnusedGlobalSymbols Used by Oolite itself
this.copyright = "(C) 2017 David Pradier";
// noinspection JSUnusedGlobalSymbols Used by Oolite itself
this.licence = "CC-NC-by-SA 4.0";
this.description = "This script is the war engine of the Diplomacy OXP.";
/* ************************** OXP public functions ********************************************************/
/**
* @return {Object.<string,FunctionId>}
* @lends worldScripts.DayDiplomacy_040_WarEngine.$getScoringFunctions
*/
this.$getScoringFunctions = function () {
return this._asf;
};
/**
* @param {FunctionId}keyword the keyword is used as a FunctionId
* @param {function}f
* @param {int}position
* @lends worldScripts.DayDiplomacy_040_WarEngine.$addScoringFunction
*/
this.$addScoringFunction = function (keyword, f, position) {
this._s.$setFunction(keyword, f);
this._asf.splice(position, 0, keyword);
};
/**
* @param {Actor}observedActor
* @param {Actor}observerActor
* @lends worldScripts.DayDiplomacy_040_WarEngine.$recalculateScores
*/
this.$recalculateScores = function (observedActor, observerActor) {
var asf = this._asf, funcs = this._F, as = this._as;
var observedId = observedActor.id, observerId = observerActor.id;
var observedAs = as[observedId] || (as[observedId] = {});
var score = observedAs[observerId] || (observedAs[observerId] = {});
var finalScore = 0, z = asf.length, z0 = z - 1;
while (z--) {
var keyword = asf[z0 - z];
var thatScore = funcs[keyword](observerActor, observedActor);
score[keyword] = thatScore;
finalScore += thatScore;
}
score.SCORE = finalScore;
};
/**
*
* @param {number}threshold
* @lends worldScripts.DayDiplomacy_040_WarEngine.$setAllianceThreshold
*/
this.$setAllianceThreshold = function (threshold) {
// warCouncilRecurrentAction is a function defined at the beginning of this WarEngine.
this._F.warCouncilRecurrentAction.allianceThreshold = threshold;
this._s._State.allianceThreshold = threshold;
};
/**
*
* @param {number}threshold
* @lends worldScripts.DayDiplomacy_040_WarEngine.$setWarThreshold
*/
this.$setWarThreshold = function (threshold) {
// warCouncilRecurrentAction is a function defined at the beginning of this WarEngine.
this._F.warCouncilRecurrentAction.warThreshold = threshold;
this._s._State.warThreshold = threshold;
};
/**
*
* @return {number}
* @lends worldScripts.DayDiplomacy_040_WarEngine.$getAllianceThreshold
*/
this.$getAllianceThreshold = function () {
return this._s._State.allianceThreshold;
};
/**
*
* @return {number}
* @lends worldScripts.DayDiplomacy_040_WarEngine.$getWarThreshold
*/
this.$getWarThreshold = function () {
return this._s._State.warThreshold;
};
/**
* Returns a dictionary with an {@link ActorId} as key, and as value: a dictionary with another {@link ActorId} as key,
* and as value: -1 is there's a war between those 2 actors, 1 if there's an alliance.
* @return {Object.<ActorId,Object.<ActorId,number>>}
* @lends worldScripts.DayDiplomacy_040_WarEngine.$getAlliancesAndWars
*/
this.$getAlliancesAndWars = function () {
return this._a;
};
/**
*
* @return {Object.<ActorId,Object.<ActorId,Object.<string,number>>>}
* @lends worldScripts.DayDiplomacy_040_WarEngine.$getScores
*/
this.$getScores = function () {
return this._as;
};
/**
* true if those 2 actors are at war
* @param {ActorId} actorIdA an actorId
* @param {ActorId} actorIdB another actorId
* @return {boolean}
* @lends worldScripts.DayDiplomacy_040_WarEngine.$areActorsWarring
*/
this.$areActorsWarring = function (actorIdA, actorIdB) {
// FIXME use hasOwnProperty ?
var tmp = this._a[actorIdA];
return tmp && tmp[actorIdB] === -1
};
/* ************************** OXP private functions *******************************************************/
/**
*
* @param {ActorId}aSystemId
* @param {ActorId}anotherSystemId
* @private
*/
this._ally = function (aSystemId, anotherSystemId) {
var a = this._a; // alliances and wars
a[aSystemId] = a[aSystemId] || {};
a[aSystemId][anotherSystemId] = 1; // Alliance
a[anotherSystemId] = a[anotherSystemId] || {};
a[anotherSystemId][aSystemId] = 1; // Alliance
this._s.$makeActorEventKnownToUniverse(aSystemId, "ALLY", [anotherSystemId]);
this._s.$makeActorEventKnownToUniverse(anotherSystemId, "ALLY", [aSystemId]);
// Commented out because closure
// log("DiplomacyWarEngine", "Alliance between " + aSystemId + " and " + anotherSystemId);
};
/**
*
* @param {ActorId}aSystemId
* @param {ActorId}anotherSystemId
* @private
*/
this._breakAlliance = function (aSystemId, anotherSystemId) {
var a = this._a; // Alliances and wars
a[aSystemId] && a[aSystemId][anotherSystemId] === 1 && (delete a[aSystemId][anotherSystemId]); // Breaking alliance
a[anotherSystemId] && a[anotherSystemId][aSystemId] === 1 && (delete a[anotherSystemId][aSystemId]); // Breaking alliance
this._s.$makeActorEventKnownToUniverse(aSystemId, "BREAK", [anotherSystemId]);
this._s.$makeActorEventKnownToUniverse(anotherSystemId, "BREAK", [aSystemId]);
// Commented out because closure
// log("DiplomacyWarEngine", "Alliance broken between " + aSystemId + " and " + anotherSystemId);
};
/**
*
* @param {ActorId}aSystemId
* @param {ActorId}anotherSystemId
* @private
*/
this._declareWar = function (aSystemId, anotherSystemId) {
var a = this._a; // Alliances and wars
a[aSystemId] = a[aSystemId] || {};
a[aSystemId][anotherSystemId] = -1; // War
a[anotherSystemId] = a[anotherSystemId] || {};
a[anotherSystemId][aSystemId] = -1; // War
this._s.$makeActorEventKnownToUniverse(aSystemId, "WAR", [anotherSystemId]);
this._s.$makeActorEventKnownToUniverse(anotherSystemId, "WAR", [aSystemId]);
// Commented out because closure
// log("DiplomacyWarEngine", "War between " + aSystemId + " and " + anotherSystemId);
};
/**
*
* @param {ActorId} aSystemId
* @param {ActorId} anotherSystemId
* @private
*/
this._makePeace = function (aSystemId, anotherSystemId) {
var a = this._a; // Alliances and wars
a[aSystemId] && a[aSystemId][anotherSystemId] === -1 && (delete a[aSystemId][anotherSystemId]); // Making peace
a[anotherSystemId] && a[anotherSystemId][aSystemId] === -1 && (delete a[anotherSystemId][aSystemId]); // Making peace
this._s.$makeActorEventKnownToUniverse(aSystemId, "PEACE", [anotherSystemId]);
this._s.$makeActorEventKnownToUniverse(anotherSystemId, "PEACE", [aSystemId]);
// Commented out because closure
// log("DiplomacyWarEngine", "Peace between " + aSystemId + " and " + anotherSystemId);
};
/**
*
* @private
*/
this._initAllyScore = function () {
var engine = this._s;
if (engine.$getEventTypes().indexOf("ALLYSCORE") === -1) {
engine.$addEventType("ALLYSCORE", 1);
// Function to calculate scores, here is the system for which scores are calculated
var diplomacyAlliancesScoringRecurrentAction = function diplomacyAlliancesScoringRecurrentAction(aSystem) {
// FIXME perfectfunc should be actor-agnostic
var observersId = aSystem.observers["SYSTEM"];
if (!observersId) {
return; // There may be no observer yet.
}
var that = diplomacyAlliancesScoringRecurrentAction;
var we = that.warEngine || (that.warEngine = worldScripts.DayDiplomacy_040_WarEngine);
var engine = that._engine || (that._engine = worldScripts.DayDiplomacy_000_Engine);
var actors = engine.$getActors();
var y = observersId.length;
while (y--) {
we.$recalculateScores(actors[observersId[y]], aSystem);
}
};
var fid = "diplomacyAlliancesScoringRecurrentAction";
engine.$setFunction(fid, diplomacyAlliancesScoringRecurrentAction);
engine.$setRecurrentAction(engine.$buildAction(engine.$getNewActionId(), "ALLYSCORE", "SYSTEM", fid));
}
};
/**
* @private
*/
this._init = function () {
var engine = this._s;
var history = worldScripts.DayDiplomacy_020_History;
if (engine.$getEventTypes().indexOf("BREAK") !== -1) {
return; // Already initialized
}
// Creating events
engine.$addEventType("WARCOUNCIL", 2);
engine.$addEventType("BREAK", 3);
engine.$addEventType("ALLY", 4);
engine.$addEventType("WAR", 5);
engine.$addEventType("PEACE", 6);
// Managing history sentences
history.$setEventFormattingFunction("BREAK",
/**
* @param {DiplomacyEvent} breakEvent
* @return {string} the formatted message
*/
function breakEventFormattingFunction(breakEvent) {
var f = breakEventFormattingFunction;
var engine = f._engine || (f._engine = worldScripts.DayDiplomacy_000_Engine);
var actors = engine.$getActors();
return actors[breakEvent.actorId].name + " broke their alliance with " + actors[breakEvent.args[0]].name + ".";
});
history.$setEventFormattingFunction("ALLY",
/**
* FIXME create a type AllyEvent?
* @param {DiplomacyEvent} allyEvent
* @return {string}
*/
function allyEventFormattingFunction(allyEvent) {
var f = allyEventFormattingFunction;
var engine = f._engine || (f._engine = worldScripts.DayDiplomacy_000_Engine);
var actors = engine.$getActors();
return actors[allyEvent.actorId].name + " allied with " + actors[allyEvent.args[0]].name + ".";
});
history.$setEventFormattingFunction("WAR",
/**
* @param {DiplomacyEvent} warEvent
* @return {string}
*/
function warEventFormattingFunction(warEvent) {
var f = warEventFormattingFunction;
var engine = f._engine || (f._engine = worldScripts.DayDiplomacy_000_Engine);
var actors = engine.$getActors();
return actors[warEvent.actorId].name + " declared war with " + actors[warEvent.args[0]].name + ".";
});
history.$setEventFormattingFunction("PEACE",
/**
* @param {DiplomacyEvent} peaceEvent
* @return {string}
*/
function peaceEventFormattingFunction(peaceEvent) {
var f = peaceEventFormattingFunction;
var engine = f._engine || (f._engine = worldScripts.DayDiplomacy_000_Engine);
var actors = engine.$getActors();
return actors[peaceEvent.actorId].name + " made peace with " + actors[peaceEvent.args[0]].name + ".";
});
// Function to ally, break alliance, declare war or peace: here, aSystem is the system to which the action might be directed.
var warCouncilRecurrentAction = function warCouncilRecurrentAction(aSystem) {
var that = warCouncilRecurrentAction;
var alliancesScores = that.alliancesScores || (that.alliancesScores = worldScripts.DayDiplomacy_000_Engine._State.alliancesScores);
var a = that.alliancesAndWars || (that.alliancesAndWars = worldScripts.DayDiplomacy_000_Engine._State.alliancesAndWars);
var allianceThreshold = that.allianceThreshold || (that.allianceThreshold = worldScripts.DayDiplomacy_000_Engine._State.allianceThreshold);
var warThreshold = that.warThreshold || (that.warThreshold = worldScripts.DayDiplomacy_000_Engine._State.warThreshold);
var aSystemId = aSystem.id;
var aSystemScores = alliancesScores[aSystemId];
var warEngine = that.warEngine || (that.warEngine = worldScripts.DayDiplomacy_040_WarEngine);
for (var targetId in aSystemScores) {
if (aSystemScores.hasOwnProperty(targetId)) {
// Alliance
if ((!a.hasOwnProperty(targetId) || !a[targetId].hasOwnProperty(aSystemId) || a[targetId][aSystemId] !== 1) // Not yet allied
&& aSystemScores[targetId].SCORE >= allianceThreshold
&& alliancesScores[targetId][aSystemId].SCORE >= allianceThreshold) { // Both are willing
warEngine._ally(aSystemId, targetId);
}
// Break
if ((a.hasOwnProperty(targetId) && a[targetId][aSystemId] === 1) // Allied
&& (aSystemScores[targetId].SCORE < allianceThreshold
|| alliancesScores[targetId][aSystemId].SCORE < allianceThreshold)) { // One is willing to break
warEngine._breakAlliance(aSystemId, targetId);
}
// War
if ((!a.hasOwnProperty(targetId) || !a[targetId].hasOwnProperty(aSystemId) || a[targetId][aSystemId] !== -1) // Not yet warring
&& (aSystemScores[targetId].SCORE <= warThreshold || alliancesScores[targetId][aSystemId].SCORE <= warThreshold)) { // One is willing
warEngine._declareWar(aSystemId, targetId);
}
// Peace
if ((a.hasOwnProperty(targetId) && a[targetId][aSystemId] === -1) // Warring
&& aSystemScores[targetId].SCORE > warThreshold && alliancesScores[targetId][aSystemId].SCORE > warThreshold) { // Both are willing
warEngine._makePeace(aSystemId, targetId);
}
}
}
};
var fid = "warCouncilRecurrentAction";
engine.$setFunction(fid, warCouncilRecurrentAction);
engine.$setRecurrentAction(engine.$buildAction(engine.$getNewActionId(), "WARCOUNCIL", "SYSTEM", fid));
this.$setAllianceThreshold(.5); // Default value for the very first initialization
this.$setWarThreshold(-1); // Default value for the very first initialization
};
this._startUp = function () {
var engine = this._s;
this._F = engine.$getFunctions();
// Alliances Scoring _Functions: { keyword => fid }
this._asf = engine.$initAndReturnSavedData("alliancesScoringFunctions", []);
// Alliances Scores: { observedId => { observerId => { keyword => score } } }
this._as = engine.$initAndReturnSavedData("alliancesScores", {});
this._a = engine.$initAndReturnSavedData("alliancesAndWars", {});
this._initAllyScore();
this._init(); // ALLY/BREAK/WAR/PEACE
this.$setAllianceThreshold(this._s._State.allianceThreshold); // Startup init using saved value
this.$setWarThreshold(this._s._State.warThreshold); // Startup init using saved value
delete this._startUp; // No need to startup twice
};
/* ************************** Oolite events ***************************************************************/
this.startUp = function () {
this._s = worldScripts.DayDiplomacy_000_Engine;
this._s.$subscribe(this.name);
delete this.startUp; // No need to startup twice
};
|