Fast Priming Keys

An area for discussing new ideas and additions to Oolite.

Moderators: another_commander, winston

Post Reply
dybal
---- E L I T E ----
---- E L I T E ----
Posts: 499
Joined: Mon Feb 10, 2020 12:47 pm

Fast Priming Keys

Post by dybal »

Greetings!

Is there a reason for not having more keys?

Cycling through the whole list of primable equipment in combat is not viable...

Couldn't the the numeric pad keys be used, or the pairing of F1-F7 with the numeric 1-7 key be broken and one set used for fast priming keys?

Cheers,

Dybal

EDIT: typo in the numeric keys range
EDIT2: Changed topic from Fast-Activating to Fast-Priming Keys, since the trouble is mostly the cycling through the primable equipments list
Last edited by dybal on Mon May 18, 2020 1:17 pm, edited 4 times in total.
User avatar
phkb
Impressively Grand Sub-Admiral
Impressively Grand Sub-Admiral
Posts: 4646
Joined: Tue Jan 21, 2014 10:37 pm
Location: Writing more OXPs, because the world needs more OXPs.

Re: Fast activation Keys

Post by phkb »

Can I point you toward the [EliteWiki] Auto-prime equipment OXP, which is designed to make life a little simpler for some pieces of prime-able equipment, by automatically priming it when the associated MFD is selected.
dybal
---- E L I T E ----
---- E L I T E ----
Posts: 499
Joined: Mon Feb 10, 2020 12:47 pm

Re: Fast Priming Keys

Post by dybal »

phkb wrote: Sat May 16, 2020 3:15 pm
Can I point you toward the [EliteWiki] Auto-prime equipment OXP, which is designed to make life a little simpler for some pieces of prime-able equipment, by automatically priming it when the associated MFD is selected.
I already use it, it helps a lot, but:

- there are primable equips without MFDs, so the player would have to cycle through the whole primable equipment list to get to them (I have 32 in the list at the moment :P )
- as I said, it helps, but the player still has to cycle through a number of MFD positions at really busy moments - even redefinig the cycling key (so it doesn't need SHIFT pressed), it's a bit of a hassle (I frequently "overshoot" the target MFD and have to cycle through again...)

Fast-Priming keys would make it instantaneous for those equips configured into them.
Last edited by dybal on Mon May 18, 2020 1:16 pm, edited 1 time in total.
another_commander
Quite Grand Sub-Admiral
Quite Grand Sub-Admiral
Posts: 6552
Joined: Wed Feb 28, 2007 7:54 am

Re: Fast activation Keys

Post by another_commander »

dybal wrote: Sat May 16, 2020 4:46 pm
it's a bit of a hassle (I frequently "overshoot" the target MFD and have to cycle through again...)
You don't have to cycle through again. Just press Ctrl+PrimeEquipmentKey to cycle the list in reverse.-
dybal
---- E L I T E ----
---- E L I T E ----
Posts: 499
Joined: Mon Feb 10, 2020 12:47 pm

Re: Fast activation Keys

Post by dybal »

another_commander wrote: Sat May 16, 2020 8:52 pm
dybal wrote: Sat May 16, 2020 4:46 pm
it's a bit of a hassle (I frequently "overshoot" the target MFD and have to cycle through again...)
You don't have to cycle through again. Just press Ctrl+PrimeEquipmentKey to cycle the list in reverse.-
That's good to know! Ctrl+ works to reverse-cycle both MFD positions (with my keyboard config) and primable equipments.
dybal
---- E L I T E ----
---- E L I T E ----
Posts: 499
Joined: Mon Feb 10, 2020 12:47 pm

Re: Fast Priming Keys

Post by dybal »

Things that would be better with a fast-priming-key configured for it:

- Cloaking device, suitable for fast-activating
- Shield Cycler, might be suitable to fast-activating (haven't used it)
- LMSS, suitable for fast-activating
- Fuel Injection Cruise Control
- Telescope or Fast Target Selector (if you don't have Telescope... it doesn't work well with Telescope), suitable for fast-activating
- Towbar
- Energy Bomb, suitable for fast-activating
- Q-Charger
- Tracker
- Escort Deck
- Waypoint Here
- Cargo Stopper
- Any equipment with a transitory output in a MFD, like RangeFinder, BroadcastComm, WarrantScanner, ManifestScanner

Thinking a bit about it, fast-priming keys would be better than fast-activating keys, it would deal better with the more complex equipments, at the price of a second key-press for the simpler ones (and the player could choose two equipments to be activated by a single key press through the two fast-activating key we have now)

The player would not have to look to see what equipment was primed when in a hurry, he/she would just press the fast-priming-key he/she configured for the equipment and then use the normal activate and mode keys.
User avatar
Milo
---- E L I T E ----
---- E L I T E ----
Posts: 466
Joined: Mon Sep 17, 2018 5:01 pm

Re: Fast Priming Keys

Post by Milo »

I agree, having hotkeys that can directly prime a chosen piece of equipment (rather than direct activation hotkeys like TAB/zero, because you sometimes want to change the mode first) would be a significant quality of life improvement for primeable equipment. I have over 40 (edit: down to 26 now!) so cycling through is quite a chore. Maybe allow a combination like holding SHIFT with the hotkey to prime it, otherwise just activate immediately (edited to add: and don't prime it, i.e., if you used a hotkey without holding SHIFT, it would activate the hotkeyed equipment, and you would keep your current position in the primeable equipment cycle). (After thinking about the opposite approach with SHIFT to activate, I concluded that once you have the mode where you want it, you are more likely to want to repeatedly activate, so not needing an extra combo key for activate would be preferred.)

Even better would be if fast priming hotkeys also activated the corresponding MFD for you, if you had it displayed in one of them. Edited to add: Actually, on reflection, I do not think that fast priming hotkeys activating an MFD is a desirable feature. I've been finding that when I want to switch MFDs, Auto Prime Equipment OXP causes me to lose my position in the primeable equipment cycle, which is particularly painful if the primeable equipment I had selected before doesn't have an MFD for quick-access. So I've removed Auto Prime Equipment from my OXPs list. I would not want the same to happen in reverse (if when cycling through primeable equipment, it changed the active MFD, that would interfere with attempts to concurrently adjust MFDs). I think it is cleaner to keep MFDs completely separate from primeable equipment (as they are today without that OXP), so I'm now retracting my suggestion at the beginning of this paragraph. I still suggest what I wrote in the paragraph above.

And speaking of MFD shortcuts, adding a reverse cycle by holding CTRL+: or CTRL+; would be a very nice improvement and would be intuitive/consistent with the CTRL-N shortcut for reverse cycling primeable equipment. I often miss the MFD I want when cycling through them and have to go another full cycle. (And I see this reverse cycling feature is already implemented for 1.89. Thanks phkb!)
User avatar
Milo
---- E L I T E ----
---- E L I T E ----
Posts: 466
Joined: Mon Sep 17, 2018 5:01 pm

Re: Fast Priming Keys

Post by Milo »

I implemented one of the features I suggested here: SHIFT+fast-activation-key to prime instead of activate.

The code diff is in my consolidated suggestions post.

The request for additional fast activation hotkeys remains to be done.
User avatar
Milo
---- E L I T E ----
---- E L I T E ----
Posts: 466
Joined: Mon Sep 17, 2018 5:01 pm

Re: Fast Priming Keys

Post by Milo »

Here is a code diff to implement two more fast activation keys, '[' and ']' and update the dockside primable manager script to allow configuration of the two additional keys. All seems fine in testing. This includes the feature introduced in my previous post to prime instead of activate with a combo key, but I changed the combo key from shift to ctrl. So ctrl+[ will prime the third fast activation key, [ will activate it, etc.

I deliberately renamed one missiontext (oolite-primablemanager-completed -> oolite-primablemanager-complete) because wildeblood's station interfaces OXP overrides the original string and therefore interferes with the display of the new configuration details. This way anyone who has that OXP can continue to use it without any issues, as the string it was overriding is no longer used. Obviously the OXP itself can be changed, but I still lack access to release an update through the OXZ Manager, so I thought of this one-character solution.

Code: Select all

diff --git a/Resources/Config/descriptions.plist b/Resources/Config/descriptions.plist
index e6a3f241..a0a34e70 100644
--- a/Resources/Config/descriptions.plist
+++ b/Resources/Config/descriptions.plist
@@ -1249,6 +1249,8 @@
 	"oolite-keydesc-key_mode_equipment"			= "Item button 2";
 	"oolite-keydesc-key_fastactivate_equipment_a" = "Item shortcut 1";
 	"oolite-keydesc-key_fastactivate_equipment_b" = "Item shortcut 2";
+	"oolite-keydesc-key_fastactivate_equipment_c" = "Item shortcut 3";
+	"oolite-keydesc-key_fastactivate_equipment_d" = "Item shortcut 4";
 
 	"oolite-keydesc-key_target_incoming_missile"	= "Target incoming";
 	"oolite-keydesc-key_target_missile"			= "Target missile";
@@ -1654,6 +1656,8 @@
 	"stickmapper-mode-equipment"	= "Set equipment mode";
 	"stickmapper-fastactivate-a"	= "Activate first fast equipment slot";
 	"stickmapper-fastactivate-b"	= "Activate second fast equipment slot";
+	"stickmapper-fastactivate-c"	= "Activate third fast equipment slot";
+	"stickmapper-fastactivate-d"	= "Activate fourth fast equipment slot";
 	"stickmapper-escape-pod"		= "Escape pod";
 	"stickmapper-cloak"				= "Cloaking device";
 	"stickmapper-scanner-zoom"		= "Scanner zoom";
diff --git a/Resources/Config/keyconfig.plist b/Resources/Config/keyconfig.plist
index cb6a131c..beee1cb7 100644
--- a/Resources/Config/keyconfig.plist
+++ b/Resources/Config/keyconfig.plist
@@ -43,6 +43,8 @@
 	key_mode_equipment			= "b";
 	key_fastactivate_equipment_a = "0";
 	key_fastactivate_equipment_b = "\t";	// tab
+	key_fastactivate_equipment_c = "[";
+	key_fastactivate_equipment_d = "]";
 
 	key_target_incoming_missile	= "T";
 	key_target_missile			= "t";
diff --git a/Resources/Config/missiontext.plist b/Resources/Config/missiontext.plist
index 3565bde2..fc58d97e 100644
--- a/Resources/Config/missiontext.plist
+++ b/Resources/Config/missiontext.plist
@@ -177,18 +177,20 @@
 	// primable equipment management
 	"oolite-primablemanager-interface-title" = "Manage primable equipment";
 	"oolite-primablemanager-interface-category" = "[interfaces-category-ship-systems]";
-	"oolite-primablemanager-interface-summary" = "Assign primable equipment fitted to your ship to the two fast activation controls, for easy access in emergencies.";
+	"oolite-primablemanager-interface-summary" = "Assign primable equipment fitted to your ship to the four fast activation controls, for easy access in emergencies.";
 	"oolite-primablemanager-next" = "Next page";
 	"oolite-primablemanager-previous" = "Previous page";
 
 	"oolite-primablemanager-page1-title" = "Set first fast activation (defensive)";
 	"oolite-primablemanager-page2-title" = "Set second fast activation (offensive)";
+	"oolite-primablemanager-page3-title" = "Set third fast activation";
+	"oolite-primablemanager-page4-title" = "Set fourth fast activation";
 	"oolite-primablemanager-setup-text" = "Select the equipment to bind to this fast activation control.";
-	"oolite-primablemanager-page3-title" = "Fast activation settings saved";
+	"oolite-primablemanager-page5-title" = "Fast activation settings saved";
 	"oolite-primablemanager-select-none" = "Assign no equipment";
 	"oolite-primablemanager-selected-text" = "(selected)";
 
-	"oolite-primablemanager-completed" = "Equipment configuration is complete.\n\nFirst fast activation (defensive):\n  [oolite-primable-a]\n\nSecond fast activation (offensive):\n  [oolite-primable-b]";
+	"oolite-primablemanager-complete" = "Equipment configuration is complete.\n\nFirst fast activation (defensive):\n  [oolite-primable-a]\n\nSecond fast activation (offensive):\n  [oolite-primable-b]\n\nThird fast activation:\n  [oolite-primable-c]\n\nFourth fast activation:\n  [oolite-primable-d]";
 
 
 	// tutorial
diff --git a/Resources/Scripts/oolite-primable-equipment-manager.js b/Resources/Scripts/oolite-primable-equipment-manager.js
index 06f3d5aa..57d886be 100644
--- a/Resources/Scripts/oolite-primable-equipment-manager.js
+++ b/Resources/Scripts/oolite-primable-equipment-manager.js
@@ -244,7 +244,7 @@ this._configureStage2 = function(choice)
 	if (choice === "ZZZZZZZ_OOLITE_1_NEXT") 
 	{
 		this.$page++;
-		this._configurePrimableEquipment();
+		this._configureStage3();
 		return;
 	}
 	if (choice === "ZZZZZZZ_OOLITE_2_PREV")
@@ -273,7 +273,7 @@ this._configureStage3 = function(choice)
 	if (choice === "ZZZZZZZ_OOLITE_1_NEXT") 
 	{
 		this.$page++;
-		this._configureStage2();
+		this._configureStage4();
 		return;
 	}
 	if (choice === "ZZZZZZZ_OOLITE_2_PREV")
@@ -287,13 +287,73 @@ this._configureStage3 = function(choice)
 		player.ship.fastEquipmentB = choice;
 		this.$page = 0;
 	}
-	var message = expandMissionText("oolite-primablemanager-completed",{
+	mission.runScreen({
+		titleKey: "oolite-primablemanager-page3-title",
+		messageKey: "oolite-primablemanager-setup-text",
+		choices: this._equipmentChoices(player.ship.fastEquipmentC),
+		initialChoicesKey: this._initialChoice(player.ship.fastEquipmentC),
+		exitScreen: "GUI_SCREEN_INTERFACES",
+		screenID: "oolite-primablemanager"
+	},this._configureStage4.bind(this));
+}
+
+this._configureStage4 = function(choice)
+{
+	if (choice === "ZZZZZZZ_OOLITE_1_NEXT") 
+	{
+		this.$page++;
+		this._configureStage5();
+		return;
+	}
+	if (choice === "ZZZZZZZ_OOLITE_2_PREV")
+	{
+		this.$page--;
+		this._configureStage3();
+		return;
+	}
+	if (choice != "" && choice != null) 
+	{
+		player.ship.fastEquipmentC = choice;
+		this.$page = 0;
+	}
+	mission.runScreen({
+		titleKey: "oolite-primablemanager-page4-title",
+		messageKey: "oolite-primablemanager-setup-text",
+		choices: this._equipmentChoices(player.ship.fastEquipmentD),
+		initialChoicesKey: this._initialChoice(player.ship.fastEquipmentD),
+		exitScreen: "GUI_SCREEN_INTERFACES",
+		screenID: "oolite-primablemanager"
+	},this._configureStage5.bind(this));
+}
+
+this._configureStage5 = function(choice)
+{
+	if (choice === "ZZZZZZZ_OOLITE_1_NEXT") 
+	{
+		this.$page++;
+		this._configurePrimableEquipment();
+		return;
+	}
+	if (choice === "ZZZZZZZ_OOLITE_2_PREV")
+	{
+		this.$page--;
+		this._configureStage4();
+		return;
+	}
+	if (choice != "" && choice != null) 
+	{
+		player.ship.fastEquipmentD = choice;
+		this.$page = 0;
+	}
+	var message = expandMissionText("oolite-primablemanager-complete",{
 		"oolite-primable-a" : this._nameEquipment(player.ship.fastEquipmentA),
-		"oolite-primable-b" : this._nameEquipment(player.ship.fastEquipmentB)
+		"oolite-primable-b" : this._nameEquipment(player.ship.fastEquipmentB),
+		"oolite-primable-c" : this._nameEquipment(player.ship.fastEquipmentC),
+		"oolite-primable-d" : this._nameEquipment(player.ship.fastEquipmentD)
 	});
 
 	mission.runScreen({
-		titleKey: "oolite-primablemanager-page3-title",
+		titleKey: "oolite-primablemanager-page5-title",
 		message: message,
 		exitScreen: "GUI_SCREEN_INTERFACES",
 		screenID: "oolite-primablemanager"
diff --git a/src/Core/Entities/PlayerEntity.h b/src/Core/Entities/PlayerEntity.h
index 4a68c657..d184d93c 100644
--- a/src/Core/Entities/PlayerEntity.h
+++ b/src/Core/Entities/PlayerEntity.h
@@ -473,6 +473,8 @@ typedef enum
 	NSUInteger				primedEquipment;
 	NSString				*_fastEquipmentA;
 	NSString				*_fastEquipmentB;
+	NSString				*_fastEquipmentC;
+	NSString				*_fastEquipmentD;
 
 	OOCargoQuantity			current_cargo;
 	
@@ -557,6 +559,8 @@ typedef enum
 	OOKeyCode				key_mode_equipment;
 	OOKeyCode				key_fastactivate_equipment_a;
 	OOKeyCode				key_fastactivate_equipment_b;
+	OOKeyCode				key_fastactivate_equipment_c;
+	OOKeyCode				key_fastactivate_equipment_d;
 	
 	OOKeyCode				key_target_missile;
 	OOKeyCode				key_untarget_missile;
@@ -997,8 +1001,12 @@ typedef enum
 - (void) activatePrimableEquipment:(NSUInteger)index withMode:(OOPrimedEquipmentMode)mode;
 - (NSString *) fastEquipmentA;
 - (NSString *) fastEquipmentB;
+- (NSString *) fastEquipmentC;
+- (NSString *) fastEquipmentD;
 - (void) setFastEquipmentA:(NSString *)eqKey;
 - (void) setFastEquipmentB:(NSString *)eqKey;
+- (void) setFastEquipmentC:(NSString *)eqKey;
+- (void) setFastEquipmentD:(NSString *)eqKey;
 
 - (OOCreditsQuantity) adjustPriceByScriptForEqKey:(NSString *)eqKey withCurrent:(OOCreditsQuantity)price;
 
diff --git a/src/Core/Entities/PlayerEntity.m b/src/Core/Entities/PlayerEntity.m
index af4a7c63..3f3000d7 100644
--- a/src/Core/Entities/PlayerEntity.m
+++ b/src/Core/Entities/PlayerEntity.m
@@ -1034,7 +1034,9 @@ NSComparisonResult marketSorterByMassUnit(id a, id b, void *market);
 	
 	[result setObject:[self fastEquipmentA] forKey:@"primed_equipment_a"];
 	[result setObject:[self fastEquipmentB] forKey:@"primed_equipment_b"];
-
+	[result setObject:[self fastEquipmentC] forKey:@"primed_equipment_c"];
+	[result setObject:[self fastEquipmentD] forKey:@"primed_equipment_d"];
+	
 	// roles
 	[result setObject:roleWeights forKey:@"role_weights"];
 
@@ -1383,7 +1385,9 @@ NSComparisonResult marketSorterByMassUnit(id a, id b, void *market);
 	
 	[self setFastEquipmentA:[dict oo_stringForKey:@"primed_equipment_a" defaultValue:@"EQ_CLOAKING_DEVICE"]];
 	[self setFastEquipmentB:[dict oo_stringForKey:@"primed_equipment_b" defaultValue:@"EQ_ENERGY_BOMB"]]; // even though there isn't one, for compatibility.
-
+	[self setFastEquipmentC:[dict oo_stringForKey:@"primed_equipment_c" defaultValue:@""]];
+	[self setFastEquipmentD:[dict oo_stringForKey:@"primed_equipment_d" defaultValue:@""]];
+	
 	if ([self hasEquipmentItemProviding:@"EQ_ADVANCED_COMPASS"])  compassMode = COMPASS_MODE_PLANET;
 	else  compassMode = COMPASS_MODE_BASIC;
 	DESTROY(compassTarget);
@@ -2114,7 +2118,9 @@ NSComparisonResult marketSorterByMassUnit(id a, id b, void *market);
 	primedEquipment = 0;
 	[self setFastEquipmentA:@"EQ_CLOAKING_DEVICE"];
 	[self setFastEquipmentB:@"EQ_ENERGY_BOMB"]; // for compatibility purposes
-
+	[self setFastEquipmentC:@""];
+	[self setFastEquipmentD:@""];
+	
 	[self setActiveMissile:0];
 	for (i = 0; i < missiles; i++)
 	{
@@ -2361,6 +2367,8 @@ NSComparisonResult marketSorterByMassUnit(id a, id b, void *market);
 	
 	DESTROY(_fastEquipmentA);
 	DESTROY(_fastEquipmentB);
+	DESTROY(_fastEquipmentC);
+	DESTROY(_fastEquipmentD);
 
 	DESTROY(eqScripts);
 	DESTROY(worldScripts);
@@ -8131,6 +8139,18 @@ NSComparisonResult marketSorterByMassUnit(id a, id b, void *market);
 }
 
 
+- (NSString *) fastEquipmentC
+{
+	return _fastEquipmentC;
+}
+
+
+- (NSString *) fastEquipmentD
+{
+	return _fastEquipmentD;
+}
+
+
 - (void) setFastEquipmentA:(NSString *)eqKey
 {
 	[_fastEquipmentA release];
@@ -8145,6 +8165,20 @@ NSComparisonResult marketSorterByMassUnit(id a, id b, void *market);
 }
 
 
+- (void) setFastEquipmentC:(NSString *)eqKey
+{
+	[_fastEquipmentC release];
+	_fastEquipmentC = [eqKey copy];
+}
+
+
+- (void) setFastEquipmentD:(NSString *)eqKey
+{
+	[_fastEquipmentD release];
+	_fastEquipmentD = [eqKey copy];
+}
+
+
 - (OOEquipmentType *) weaponTypeForFacing:(OOWeaponFacing)facing strict:(BOOL)strict
 {
 	OOWeaponType weaponType = nil;
@@ -9930,6 +9964,7 @@ static NSString *last_outfitting_key=nil;
 		 @"",@"",@"",
 		 @"key_prime_equipment",@"key_activate_equipment",@"key_mode_equipment",
 		 @"key_fastactivate_equipment_a",@"key_fastactivate_equipment_b",@"", //
+		 @"key_fastactivate_equipment_c",@"key_fastactivate_equipment_d",@"", //
 #if OO_FOV_INFLIGHT_CONTROL_ENABLED
 		@"key_inc_field_of_view",@"key_dec_field_of_view",@"",
 #else
@@ -13239,6 +13274,8 @@ else _dockTarget = NO_TARGET;
 	key_mode_equipment &&
 	key_fastactivate_equipment_a &&
 	key_fastactivate_equipment_b &&
+	key_fastactivate_equipment_c &&
+	key_fastactivate_equipment_d &&
 	key_target_missile &&
 	key_untarget_missile &&
 	key_target_incoming_missile &&
diff --git a/src/Core/Entities/PlayerEntityControls.m b/src/Core/Entities/PlayerEntityControls.m
index fa5e51b8..e647d912 100644
--- a/src/Core/Entities/PlayerEntityControls.m
+++ b/src/Core/Entities/PlayerEntityControls.m
@@ -81,6 +81,8 @@ static BOOL				activate_equipment_pressed;
 static BOOL				mode_equipment_pressed;
 static BOOL				fastactivate_a_pressed;
 static BOOL				fastactivate_b_pressed;
+static BOOL				fastactivate_c_pressed;
+static BOOL				fastactivate_d_pressed;
 static BOOL				next_missile_pressed;
 static BOOL				fire_missile_pressed;
 static BOOL				target_missile_pressed;
@@ -280,6 +282,8 @@ static NSTimeInterval	time_last_frame;
 	LOAD_KEY_SETTING(key_mode_equipment,		'b'			);
 	LOAD_KEY_SETTING_ALIAS(key_fastactivate_equipment_a, key_cloaking_device,		'0'			);
 	LOAD_KEY_SETTING_ALIAS(key_fastactivate_equipment_b, key_energy_bomb,			'\t'		);
+	LOAD_KEY_SETTING(key_fastactivate_equipment_c,		'['	);
+	LOAD_KEY_SETTING(key_fastactivate_equipment_d,		']'	);
 	
 	LOAD_KEY_SETTING(key_target_missile,		't'			);
 	LOAD_KEY_SETTING(key_untarget_missile,		'u'			);
@@ -1162,9 +1166,9 @@ static NSTimeInterval	time_last_frame;
 				{
 					if (!fastactivate_a_pressed)
 					{
-						// if Shift is held down at the same time as the fast activation key,
+						// if Ctrl is held down at the same time as the fast activation key,
 						// prime the relevant equipment instead of activating it
-						if (![gameView isShiftDown])
+						if (![gameView isCtrlDown])
 						{
 							[self activatePrimableEquipment:[self eqScriptIndexForKey:[self fastEquipmentA]] withMode:OOPRIMEDEQUIP_ACTIVATED];
 						}
@@ -1186,9 +1190,9 @@ static NSTimeInterval	time_last_frame;
 				{
 					if (!fastactivate_b_pressed)
 					{
-						// if Shift is held down at the same time as the fast activation key,
+						// if Ctrl is held down at the same time as the fast activation key,
 						// prime the relevant equipment instead of activating it
-						if (![gameView isShiftDown])
+						if (![gameView isCtrlDown])
 						{
 							[self activatePrimableEquipment:[self eqScriptIndexForKey:[self fastEquipmentB]] withMode:OOPRIMEDEQUIP_ACTIVATED];
 						}
@@ -1205,6 +1209,53 @@ static NSTimeInterval	time_last_frame;
 				}
 				else fastactivate_b_pressed = NO;
 
+				exceptionContext = @"fast equipment C";
+				if ([gameView isDown:key_fastactivate_equipment_c] || joyButtonState[BUTTON_FASTACTIVATE_C])
+				{
+					if (!fastactivate_c_pressed)
+					{
+						// if Ctrl is held down at the same time as the fast activation key,
+						// prime the relevant equipment instead of activating it
+						if (![gameView isCtrlDown])
+						{
+							[self activatePrimableEquipment:[self eqScriptIndexForKey:[self fastEquipmentC]] withMode:OOPRIMEDEQUIP_ACTIVATED];
+						}
+						else
+						{
+							primedEquipment = [self eqScriptIndexForKey:[self fastEquipmentC]];
+							[self playNextEquipmentSelected];
+							NSString *equipmentName = [[OOEquipmentType equipmentTypeWithIdentifier:[[eqScripts oo_arrayAtIndex:primedEquipment] oo_stringAtIndex:0]] name];
+							[UNIVERSE addMessage:OOExpandKey(@"equipment-primed", equipmentName) forCount:2.0];
+							[self doScriptEvent:OOJSID("playerChangedPrimedEquipment") withArgument:[self fastEquipmentC]];
+						}
+					}
+					fastactivate_c_pressed = YES;
+				}
+				else fastactivate_c_pressed = NO;
+
+				exceptionContext = @"fast equipment D";
+				if ([gameView isDown:key_fastactivate_equipment_d] || joyButtonState[BUTTON_FASTACTIVATE_D])
+				{
+					if (!fastactivate_d_pressed)
+					{
+						// if Ctrl is held down at the same time as the fast activation key,
+						// prime the relevant equipment instead of activating it
+						if (![gameView isCtrlDown])
+						{
+							[self activatePrimableEquipment:[self eqScriptIndexForKey:[self fastEquipmentD]] withMode:OOPRIMEDEQUIP_ACTIVATED];
+						}
+						else
+						{
+							primedEquipment = [self eqScriptIndexForKey:[self fastEquipmentD]];
+							[self playNextEquipmentSelected];
+							NSString *equipmentName = [[OOEquipmentType equipmentTypeWithIdentifier:[[eqScripts oo_arrayAtIndex:primedEquipment] oo_stringAtIndex:0]] name];
+							[UNIVERSE addMessage:OOExpandKey(@"equipment-primed", equipmentName) forCount:2.0];
+							[self doScriptEvent:OOJSID("playerChangedPrimedEquipment") withArgument:[self fastEquipmentD]];
+						}
+					}
+					fastactivate_d_pressed = YES;
+				}
+				else fastactivate_d_pressed = NO;
 
 				exceptionContext = @"incoming missile T";
 				// target nearest incoming missile 'T' - useful for quickly giving a missile target to turrets
diff --git a/src/Core/Entities/PlayerEntityStickMapper.m b/src/Core/Entities/PlayerEntityStickMapper.m
index b9236370..ff39d2a2 100644
--- a/src/Core/Entities/PlayerEntityStickMapper.m
+++ b/src/Core/Entities/PlayerEntityStickMapper.m
@@ -565,6 +565,16 @@ MA 02110-1301, USA.
 				  allowable:HW_BUTTON
 					 axisfn:STICK_NOFUNCTION
 					  butfn:BUTTON_ENERGYBOMB]];
+	[funcList addObject:
+	 [self makeStickGuiDict:DESC(@"stickmapper-fastactivate-c")
+				  allowable:HW_BUTTON
+					 axisfn:STICK_NOFUNCTION
+					  butfn:BUTTON_FASTACTIVATE_C]];
+	[funcList addObject:
+	 [self makeStickGuiDict:DESC(@"stickmapper-fastactivate-d")
+				  allowable:HW_BUTTON
+					 axisfn:STICK_NOFUNCTION
+					  butfn:BUTTON_FASTACTIVATE_D]];
 	[funcList addObject:
 	 [self makeStickGuiDict:DESC(@"stickmapper-ECM")
 				  allowable:HW_BUTTON
diff --git a/src/Core/OOJoystickManager.h b/src/Core/OOJoystickManager.h
index 063ecefc..1e802ebb 100644
--- a/src/Core/OOJoystickManager.h
+++ b/src/Core/OOJoystickManager.h
@@ -99,6 +99,8 @@ enum {
 	BUTTON_DEC_FIELD_OF_VIEW,
 #endif
 	BUTTON_DOCKINGCLEARANCE,
+	BUTTON_FASTACTIVATE_C,
+	BUTTON_FASTACTIVATE_D,
 	BUTTON_end
 };
 
diff --git a/src/Core/Scripting/OOJSPlayerShip.m b/src/Core/Scripting/OOJSPlayerShip.m
index 95c696b4..6a5a4379 100644
--- a/src/Core/Scripting/OOJSPlayerShip.m
+++ b/src/Core/Scripting/OOJSPlayerShip.m
@@ -118,6 +118,8 @@ enum
 	kPlayerShip_dockedStation,					// docked station, entity, read-only
 	kPlayerShip_fastEquipmentA,					// fast equipment A, string, read/write
 	kPlayerShip_fastEquipmentB,					// fast equipment B, string, read/write
+	kPlayerShip_fastEquipmentC,					// fast equipment C, string, read/write
+	kPlayerShip_fastEquipmentD,					// fast equipment D, string, read/write
 	kPlayerShip_forwardShield,					// forward shield charge level, nonnegative float, read/write
 	kPlayerShip_forwardShieldRechargeRate,		// forward shield recharge rate, positive float, read-only
 	kPlayerShip_fuelLeakRate,					// fuel leak rate, float, read/write
@@ -187,6 +189,8 @@ static JSPropertySpec sPlayerShipProperties[] =
 	{ "dockedStation",					kPlayerShip_dockedStation,					OOJS_PROP_READONLY_CB },
 	{ "fastEquipmentA",					kPlayerShip_fastEquipmentA,					OOJS_PROP_READWRITE_CB },
 	{ "fastEquipmentB",					kPlayerShip_fastEquipmentB,					OOJS_PROP_READWRITE_CB },
+	{ "fastEquipmentC",					kPlayerShip_fastEquipmentC,					OOJS_PROP_READWRITE_CB },
+	{ "fastEquipmentD",					kPlayerShip_fastEquipmentD,					OOJS_PROP_READWRITE_CB },
 	{ "forwardShield",					kPlayerShip_forwardShield,					OOJS_PROP_READWRITE_CB },
 	{ "forwardShieldRechargeRate",		kPlayerShip_forwardShieldRechargeRate,		OOJS_PROP_READWRITE_CB },
 	{ "fuelLeakRate",					kPlayerShip_fuelLeakRate,					OOJS_PROP_READWRITE_CB },
@@ -411,6 +415,14 @@ static JSBool PlayerShipGetProperty(JSContext *context, JSObject *this, jsid pro
 			result = [player fastEquipmentB];
 			break;
 
+		case kPlayerShip_fastEquipmentC:
+			result = [player fastEquipmentC];
+			break;
+
+		case kPlayerShip_fastEquipmentD:
+			result = [player fastEquipmentD];
+			break;
+
 		case kPlayerShip_primedEquipment:
 			result = [player currentPrimedEquipment];
 			break;
@@ -788,6 +800,24 @@ static JSBool PlayerShipSetProperty(JSContext *context, JSObject *this, jsid pro
 			}
 			break;
 
+		case kPlayerShip_fastEquipmentC:
+			sValue = OOStringFromJSValue(context, *value);
+			if (sValue != nil)
+			{
+				[player setFastEquipmentC:sValue];
+				return YES;
+			}
+			break;
+
+		case kPlayerShip_fastEquipmentD:
+			sValue = OOStringFromJSValue(context, *value);
+			if (sValue != nil)
+			{
+				[player setFastEquipmentD:sValue];
+				return YES;
+			}
+			break;
+
 		case kPlayerShip_primedEquipment:
 			sValue = OOStringFromJSValue(context, *value);
 			if (sValue != nil)
For any other users of cag's scripts, here is the revised version of oolite-primable-equipment-manager.js which goes in Resources\Scripts (my code above includes a modified version of the core script that is functionally identical but doesn't include cag's optimizations):

Code: Select all

/*

oolite-primable-equipment-manager.js

Allocate primable equipment to the two 'fast activate' buttons.


Oolite
Copyright © 2004-2013 Giles C Williams and contributors

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
MA 02110-1301, USA.

*/


/*jslint white: true, undef: true, eqeqeq: true, bitwise: true, regexp: true, newcap: true, immed: true */
/*global player*/



this.name			= "oolite-primable-equipment-register";
this.author			= "cim";
this.copyright		= "© 2008-2013 the Oolite team.";

(function(){
"use strict";

this.startUpComplete = this.shipDockedWithStation = this.playerBoughtNewShip = function shipDockedWithStation()
{
	this._initialiseInterface();
}


this.playerBoughtEquipment = function playerBoughtEquipment(eqkey)
{
	this._initialiseInterface();
	this._updatePrimableEquipmentSettings(eqkey,false);
}


this._updatePrimableEquipmentSettings = function _updatePrimableEquipmentSettings(eqkey,quiet)
{
	var that = _updatePrimableEquipmentSettings;
	var msg = (that.msg = that.msg || {});

	/* If the player has gained some equipment which is recommended
	 * for fast activation, and the activation slot has nothing in it,
	 * assign to that slot. */
	var info = EquipmentInfo.infoForKey(eqkey);
	
	var fastEq_status = player.ship.equipmentStatus(player.ship.fastEquipmentA);
	if (info.fastAffinityDefensive && fastEq_status !== "EQUIPMENT_OK" && fastEq_status !== "EQUIPMENT_DAMAGED")
	{
		// no installed equipment in fast slot A, so assign this
		player.ship.fastEquipmentA = eqkey;
		if (!quiet)
		{
			msg["oolite-primable-equipment"] = info.name;
			msg["oolite-primable-slot"] = expandDescription("[oolite-primablemanager-slot-defensive]");
			player.consoleMessage(expandDescription("[oolite-primablemanager-notify-assign]",msg),7.5);
			player.consoleMessage(expandDescription("[oolite-primablemanager-notify-setup]"),7.5);
		}
		return;
	} 
	fastEq_status = player.ship.equipmentStatus(player.ship.fastEquipmentB);
	if (info.fastAffinityOffensive && fastEq_status !== "EQUIPMENT_OK" && fastEq_status !== "EQUIPMENT_DAMAGED")
	{
		// no installed equipment in fast slot B, so assign this
		player.ship.fastEquipmentB = eqkey;
		if (!quiet)
		{
			msg["oolite-primable-equipment"] = info.name;
			msg["oolite-primable-slot"] = expandDescription("[oolite-primablemanager-slot-offensive]");
			player.consoleMessage(expandDescription("[oolite-primablemanager-notify-assign]",msg),7.5);
			player.consoleMessage(expandDescription("[oolite-primablemanager-notify-setup]"),7.5);
		}
	}

}


this._initialiseInterface = function _initialiseInterface()
{
	var that = _initialiseInterface;
	var screen = (that.screen = that.screen || {});

	if (player.ship.dockedStation)
	{
		if (this._equipmentWithScripts().length > 0)
		{
			screen.title = expandMissionText("oolite-primablemanager-interface-title");
			screen.category = expandMissionText("oolite-primablemanager-interface-category");
			screen.summary = expandMissionText("oolite-primablemanager-interface-summary");
			screen.callback = this._configurePrimableEquipment.bind(this);
			player.ship.dockedStation.setInterface("oolite-primable-equipment-manager",screen);
		}
		else
			player.ship.dockedStation.setInterface("oolite-primable-equipment-manager",null);
	}
}


this._equipmentWithScripts = function _equipmentWithScripts()
{
	var that = _equipmentWithScripts;
	var result = (that.result = that.result || []);

	result.length = 0; // re-use array
	var equipment = player.ship.equipment;
	var piece, len = equipment.length;
	for (var i=0;i<len;i++)
	{
		piece = equipment[i];
		if (piece.scriptName !== "")
		{
			result.push(piece);
		}
	}
	return result;
}


this._nameEquipment = function _nameEquipment(key) 
{
	var equipment = this._equipmentWithScripts();
	var piece, len = equipment.length;
	for (var i=0;i<len;i++)
	{
		piece = equipment[i];
		if (piece.equipmentKey == key)
		{
			return piece.name;
		}
	}	
	return expandMissionText("oolite-primablemanager-select-none");
}

this.$choice_pool = [];
this._get_choice = function _get_choice(name) {
    if (this.$choice_pool.length > 0) {
        let choice = this.$choice_pool.pop();
		choice.text = name;
        return choice;
    }
    return {text: name};
}.bind(this)
	
this._free_choice = function _free_choice(c) {
    if (c !== null) {
		c.text = null;
		c.color = null;
        this.$choice_pool.push(c);
    }
}.bind(this)


this._equipmentChoices = function _equipmentChoices(current) {
	var that = _equipmentChoices;
	var _get_choice = (that._get_choice = that._get_choice || this._get_choice);
	var _free_choice = (that._free_choice = that._free_choice || this._free_choice);
	var choices = (that.choices = that.choices || {});

	for (var x in choices) // re-use object
		if (choices.hasOwnProperty(x)) _free_choice(choices[x]);
	var equipment = this._equipmentWithScripts();
	var len = equipment.length;
	var chosen = false;
	var choice, piece;
	for (var i=0;i<len;i++)
	{
		piece = equipment[i];
		choice = _get_choice(piece.name);
		if (piece.equipmentKey == current)
		{
			choice.color = "greenColor";
			choice.text += " "+expandMissionText("oolite-primablemanager-selected-text");
			chosen = true;
		}
		choices[piece.equipmentKey] = choice;
	}
	choice = _get_choice(expandMissionText("oolite-primablemanager-select-none"));
	if (!chosen)
	{
		choice.color = "greenColor";
		choice.text += " "+expandMissionText("oolite-primablemanager-selected-text");
	}
	choices.ZZZZZZ_OOLITE_EQ_NONE = choice;

	return choices;
}


this._initialChoice = function _initialChoice(key)
{
	var status = player.ship.equipmentStatus(key);
	if (status === "EQUIPMENT_OK" || status === "EQUIPMENT_DAMAGED")
	{
		return key;
	}
	return "ZZZZZZ_OOLITE_EQ_NONE";
}


this._configurePrimableEquipment = function _configurePrimableEquipment()
{
	var that = _configurePrimableEquipment;
	var stage1 = (that.stage1 = that.stage1 || {});

	stage1.titleKey = "oolite-primablemanager-page1-title";
	stage1.messageKey = "oolite-primablemanager-setup-text";
	stage1.choices = this._equipmentChoices(player.ship.fastEquipmentA);
	stage1.initialChoicesKey = this._initialChoice(player.ship.fastEquipmentA);
	stage1.exitScreen = "GUI_SCREEN_INTERFACES";
	stage1.screenID = "oolite-primablemanager";
	mission.runScreen(stage1,this._configureStage2.bind(this));
}


this._configureStage2 = function _configureStage2(choice)
{
	var that = _configureStage2;
	var stage2 = (that.stage2 = that.stage2 || {});

	if (choice !== "") 
	{
		player.ship.fastEquipmentA = choice;
	}
	
	stage2.titleKey = "oolite-primablemanager-page2-title";
	stage2.messageKey = "oolite-primablemanager-setup-text";
	stage2.choices = this._equipmentChoices(player.ship.fastEquipmentB);
	stage2.initialChoicesKey = this._initialChoice(player.ship.fastEquipmentB);
	stage2.exitScreen = "GUI_SCREEN_INTERFACES";
	stage2.screenID = "oolite-primablemanager";
	mission.runScreen(stage2,this._configureStage3.bind(this));
}

this._configureStage3 = function _configureStage3(choice)
{
	var that = _configureStage3;
	var stage3 = (that.stage3 = that.stage3 || {});

	if (choice !== "") 
	{
		player.ship.fastEquipmentB = choice;
	}
	
	stage3.titleKey = "oolite-primablemanager-page3-title";
	stage3.messageKey = "oolite-primablemanager-setup-text";
	stage3.choices = this._equipmentChoices(player.ship.fastEquipmentC);
	stage3.initialChoicesKey = this._initialChoice(player.ship.fastEquipmentC);
	stage3.exitScreen = "GUI_SCREEN_INTERFACES";
	stage3.screenID = "oolite-primablemanager";
	mission.runScreen(stage3,this._configureStage4.bind(this));
}

this._configureStage4 = function _configureStage4(choice)
{
	var that = _configureStage4;
	var stage4 = (that.stage4 = that.stage4 || {});

	if (choice !== "") 
	{
		player.ship.fastEquipmentC = choice;
	}
	
	stage4.titleKey = "oolite-primablemanager-page4-title";
	stage4.messageKey = "oolite-primablemanager-setup-text";
	stage4.choices = this._equipmentChoices(player.ship.fastEquipmentD);
	stage4.initialChoicesKey = this._initialChoice(player.ship.fastEquipmentD);
	stage4.exitScreen = "GUI_SCREEN_INTERFACES";
	stage4.screenID = "oolite-primablemanager";
	mission.runScreen(stage4,this._configureStage5.bind(this));
}

this._configureStage5 = function _configureStage5(choice)
{
	var that = _configureStage5;
	var stage5 = (that.stage5 = that.stage5 || {});
	var final_msg = (that.final_msg = that.final_msg || {});

	if (choice !== "") 
	{
		player.ship.fastEquipmentD = choice;
	}
	final_msg["oolite-primable-a"] = this._nameEquipment(player.ship.fastEquipmentA);
	final_msg["oolite-primable-b"] = this._nameEquipment(player.ship.fastEquipmentB);
	final_msg["oolite-primable-c"] = this._nameEquipment(player.ship.fastEquipmentC);
	final_msg["oolite-primable-d"] = this._nameEquipment(player.ship.fastEquipmentD);
	var message = expandMissionText("oolite-primablemanager-complete",final_msg);

	stage5.titleKey = "oolite-primablemanager-page5-title";
	stage5.message = message;
	stage5.exitScreen = "GUI_SCREEN_INTERFACES";
	stage5.screenID = "oolite-primablemanager";
	mission.runScreen(stage5);
}

}).call(this);
Last edited by Milo on Sun Jul 19, 2020 6:50 pm, edited 1 time in total.
User avatar
Cholmondely
Archivist
Archivist
Posts: 4998
Joined: Tue Jul 07, 2020 11:00 am
Location: The Delightful Domains of His Most Britannic Majesty (industrial? agricultural? mainly anything?)
Contact:

Re: Fast Priming Keys

Post by Cholmondely »

I'd been toying with the idea of linking the Elgato Stream Deck to the computer to handle some of these issues.

Alas, I'm an utter neophyte as regards programming.

Would I be correct in assuming from these high-falutin' discussions that one would be unable to immediately select the Market Inquirer MFD and within that then select the Luxuries display (for example) by pressing just the one button on the crepuscular contraption?

Or is there some sort of 'reset' for the cycling of primeable delectables which would allow this (no matter which part of this cycling was currently selected)?

Yours faithfully,

C
Comments wanted:
Missing OXPs? What do you think is missing?
Lore: The economics of ship building How many built for Aronar?
Lore: The Space Traders Flight Training Manual: Cowell & MgRath Do you agree with Redspear?
User avatar
Milo
---- E L I T E ----
---- E L I T E ----
Posts: 466
Joined: Mon Sep 17, 2018 5:01 pm

Re: Fast Priming Keys

Post by Milo »

What you suggest would require a shortcut to directly access the "mode" function of the primable equipment linked to the fast activation hotkey. What we have now only lets you access the "activate" function with a hotkey, but if you want the mode, you first have to prime it (which means it becomes your current selection, replacing whatever you selected before by cycling with N / ctrl-N) and then use the mode button (default 'b').

I added a shortcut to prime the hotkeyed equipment by holding ctrl. So if you had market inquirer as your TAB hotkey, you could press ctrl+TAB to prime it, then cycle the mode (which it uses to cycle which commodity is displayed) with 'b'.
dybal
---- E L I T E ----
---- E L I T E ----
Posts: 499
Joined: Mon Feb 10, 2020 12:47 pm

Re: Fast Priming Keys

Post by dybal »

Milo wrote: Fri Jul 17, 2020 1:53 am
Here is a code diff to implement two more fast activation keys, '[' and ']' and update the dockside primable manager script to allow configuration of the two additional keys. All seems fine in testing. This includes the feature introduced in my previous post to prime instead of activate with a combo key, but I changed the combo key from shift to ctrl. So ctrl+[ will prime the third fast activation key, [ will activate it, etc.
Do the ctrl+key to prime instead of activate works for the first two fast activating keys too?

I have them mapped to buttons in my joystick, I want to see if I can use the combination keyboard ctrl _+ joystick button...
User avatar
Milo
---- E L I T E ----
---- E L I T E ----
Posts: 466
Joined: Mon Sep 17, 2018 5:01 pm

Re: Fast Priming Keys

Post by Milo »

Yes, I did it for all of them. Should work with joystick buttons too. Of course you'd have to apply my code diff and compile it, since it's not in the official build yet.
User avatar
Milo
---- E L I T E ----
---- E L I T E ----
Posts: 466
Joined: Mon Sep 17, 2018 5:01 pm

Re: Fast Priming Keys

Post by Milo »

Cholmondely wrote: Fri Jul 17, 2020 1:38 pm
immediately select the Market Inquirer MFD and within that then select the Luxuries display (for example) by pressing just the one button on the crepuscular contraption?
This got me thinking, and I realized that there is no reason to prime the equipment when we can instead directly access the mode function. Thank you for setting me straight on this!

Here is a revised version of the code that triggers the mode function of the equipment when you hold down control along with the fast activation key. This is a complete replacement of the code diff above, but the only changes are in PlayerEntityControls.m.

With this, you can indeed use a single button to cycle the Market Inquirer MFD, for example. Moreover, whatever you had primed with N/Ctrl-N will remain selected, so you can effectively have 5 primeable equipment items simultaneously accessible in this way.

Code: Select all

diff --git a/Resources/Config/descriptions.plist b/Resources/Config/descriptions.plist
index e6a3f241..a0a34e70 100644
--- a/Resources/Config/descriptions.plist
+++ b/Resources/Config/descriptions.plist
@@ -1249,6 +1249,8 @@
 	"oolite-keydesc-key_mode_equipment"			= "Item button 2";
 	"oolite-keydesc-key_fastactivate_equipment_a" = "Item shortcut 1";
 	"oolite-keydesc-key_fastactivate_equipment_b" = "Item shortcut 2";
+	"oolite-keydesc-key_fastactivate_equipment_c" = "Item shortcut 3";
+	"oolite-keydesc-key_fastactivate_equipment_d" = "Item shortcut 4";
 
 	"oolite-keydesc-key_target_incoming_missile"	= "Target incoming";
 	"oolite-keydesc-key_target_missile"			= "Target missile";
@@ -1654,6 +1656,8 @@
 	"stickmapper-mode-equipment"	= "Set equipment mode";
 	"stickmapper-fastactivate-a"	= "Activate first fast equipment slot";
 	"stickmapper-fastactivate-b"	= "Activate second fast equipment slot";
+	"stickmapper-fastactivate-c"	= "Activate third fast equipment slot";
+	"stickmapper-fastactivate-d"	= "Activate fourth fast equipment slot";
 	"stickmapper-escape-pod"		= "Escape pod";
 	"stickmapper-cloak"				= "Cloaking device";
 	"stickmapper-scanner-zoom"		= "Scanner zoom";
diff --git a/Resources/Config/keyconfig.plist b/Resources/Config/keyconfig.plist
index cb6a131c..beee1cb7 100644
--- a/Resources/Config/keyconfig.plist
+++ b/Resources/Config/keyconfig.plist
@@ -43,6 +43,8 @@
 	key_mode_equipment			= "b";
 	key_fastactivate_equipment_a = "0";
 	key_fastactivate_equipment_b = "\t";	// tab
+	key_fastactivate_equipment_c = "[";
+	key_fastactivate_equipment_d = "]";
 
 	key_target_incoming_missile	= "T";
 	key_target_missile			= "t";
diff --git a/Resources/Config/missiontext.plist b/Resources/Config/missiontext.plist
index 3565bde2..fc58d97e 100644
--- a/Resources/Config/missiontext.plist
+++ b/Resources/Config/missiontext.plist
@@ -177,18 +177,20 @@
 	// primable equipment management
 	"oolite-primablemanager-interface-title" = "Manage primable equipment";
 	"oolite-primablemanager-interface-category" = "[interfaces-category-ship-systems]";
-	"oolite-primablemanager-interface-summary" = "Assign primable equipment fitted to your ship to the two fast activation controls, for easy access in emergencies.";
+	"oolite-primablemanager-interface-summary" = "Assign primable equipment fitted to your ship to the four fast activation controls, for easy access in emergencies.";
 	"oolite-primablemanager-next" = "Next page";
 	"oolite-primablemanager-previous" = "Previous page";
 
 	"oolite-primablemanager-page1-title" = "Set first fast activation (defensive)";
 	"oolite-primablemanager-page2-title" = "Set second fast activation (offensive)";
+	"oolite-primablemanager-page3-title" = "Set third fast activation";
+	"oolite-primablemanager-page4-title" = "Set fourth fast activation";
 	"oolite-primablemanager-setup-text" = "Select the equipment to bind to this fast activation control.";
-	"oolite-primablemanager-page3-title" = "Fast activation settings saved";
+	"oolite-primablemanager-page5-title" = "Fast activation settings saved";
 	"oolite-primablemanager-select-none" = "Assign no equipment";
 	"oolite-primablemanager-selected-text" = "(selected)";
 
-	"oolite-primablemanager-completed" = "Equipment configuration is complete.\n\nFirst fast activation (defensive):\n  [oolite-primable-a]\n\nSecond fast activation (offensive):\n  [oolite-primable-b]";
+	"oolite-primablemanager-complete" = "Equipment configuration is complete.\n\nFirst fast activation (defensive):\n  [oolite-primable-a]\n\nSecond fast activation (offensive):\n  [oolite-primable-b]\n\nThird fast activation:\n  [oolite-primable-c]\n\nFourth fast activation:\n  [oolite-primable-d]";
 
 
 	// tutorial
diff --git a/Resources/Scripts/oolite-primable-equipment-manager.js b/Resources/Scripts/oolite-primable-equipment-manager.js
index 06f3d5aa..57d886be 100644
--- a/Resources/Scripts/oolite-primable-equipment-manager.js
+++ b/Resources/Scripts/oolite-primable-equipment-manager.js
@@ -244,7 +244,7 @@ this._configureStage2 = function(choice)
 	if (choice === "ZZZZZZZ_OOLITE_1_NEXT") 
 	{
 		this.$page++;
-		this._configurePrimableEquipment();
+		this._configureStage3();
 		return;
 	}
 	if (choice === "ZZZZZZZ_OOLITE_2_PREV")
@@ -273,7 +273,7 @@ this._configureStage3 = function(choice)
 	if (choice === "ZZZZZZZ_OOLITE_1_NEXT") 
 	{
 		this.$page++;
-		this._configureStage2();
+		this._configureStage4();
 		return;
 	}
 	if (choice === "ZZZZZZZ_OOLITE_2_PREV")
@@ -287,13 +287,73 @@ this._configureStage3 = function(choice)
 		player.ship.fastEquipmentB = choice;
 		this.$page = 0;
 	}
-	var message = expandMissionText("oolite-primablemanager-completed",{
+	mission.runScreen({
+		titleKey: "oolite-primablemanager-page3-title",
+		messageKey: "oolite-primablemanager-setup-text",
+		choices: this._equipmentChoices(player.ship.fastEquipmentC),
+		initialChoicesKey: this._initialChoice(player.ship.fastEquipmentC),
+		exitScreen: "GUI_SCREEN_INTERFACES",
+		screenID: "oolite-primablemanager"
+	},this._configureStage4.bind(this));
+}
+
+this._configureStage4 = function(choice)
+{
+	if (choice === "ZZZZZZZ_OOLITE_1_NEXT") 
+	{
+		this.$page++;
+		this._configureStage5();
+		return;
+	}
+	if (choice === "ZZZZZZZ_OOLITE_2_PREV")
+	{
+		this.$page--;
+		this._configureStage3();
+		return;
+	}
+	if (choice != "" && choice != null) 
+	{
+		player.ship.fastEquipmentC = choice;
+		this.$page = 0;
+	}
+	mission.runScreen({
+		titleKey: "oolite-primablemanager-page4-title",
+		messageKey: "oolite-primablemanager-setup-text",
+		choices: this._equipmentChoices(player.ship.fastEquipmentD),
+		initialChoicesKey: this._initialChoice(player.ship.fastEquipmentD),
+		exitScreen: "GUI_SCREEN_INTERFACES",
+		screenID: "oolite-primablemanager"
+	},this._configureStage5.bind(this));
+}
+
+this._configureStage5 = function(choice)
+{
+	if (choice === "ZZZZZZZ_OOLITE_1_NEXT") 
+	{
+		this.$page++;
+		this._configurePrimableEquipment();
+		return;
+	}
+	if (choice === "ZZZZZZZ_OOLITE_2_PREV")
+	{
+		this.$page--;
+		this._configureStage4();
+		return;
+	}
+	if (choice != "" && choice != null) 
+	{
+		player.ship.fastEquipmentD = choice;
+		this.$page = 0;
+	}
+	var message = expandMissionText("oolite-primablemanager-complete",{
 		"oolite-primable-a" : this._nameEquipment(player.ship.fastEquipmentA),
-		"oolite-primable-b" : this._nameEquipment(player.ship.fastEquipmentB)
+		"oolite-primable-b" : this._nameEquipment(player.ship.fastEquipmentB),
+		"oolite-primable-c" : this._nameEquipment(player.ship.fastEquipmentC),
+		"oolite-primable-d" : this._nameEquipment(player.ship.fastEquipmentD)
 	});
 
 	mission.runScreen({
-		titleKey: "oolite-primablemanager-page3-title",
+		titleKey: "oolite-primablemanager-page5-title",
 		message: message,
 		exitScreen: "GUI_SCREEN_INTERFACES",
 		screenID: "oolite-primablemanager"
diff --git a/src/Core/Entities/PlayerEntity.h b/src/Core/Entities/PlayerEntity.h
index 4a68c657..d184d93c 100644
--- a/src/Core/Entities/PlayerEntity.h
+++ b/src/Core/Entities/PlayerEntity.h
@@ -473,6 +473,8 @@ typedef enum
 	NSUInteger				primedEquipment;
 	NSString				*_fastEquipmentA;
 	NSString				*_fastEquipmentB;
+	NSString				*_fastEquipmentC;
+	NSString				*_fastEquipmentD;
 
 	OOCargoQuantity			current_cargo;
 	
@@ -557,6 +559,8 @@ typedef enum
 	OOKeyCode				key_mode_equipment;
 	OOKeyCode				key_fastactivate_equipment_a;
 	OOKeyCode				key_fastactivate_equipment_b;
+	OOKeyCode				key_fastactivate_equipment_c;
+	OOKeyCode				key_fastactivate_equipment_d;
 	
 	OOKeyCode				key_target_missile;
 	OOKeyCode				key_untarget_missile;
@@ -997,8 +1001,12 @@ typedef enum
 - (void) activatePrimableEquipment:(NSUInteger)index withMode:(OOPrimedEquipmentMode)mode;
 - (NSString *) fastEquipmentA;
 - (NSString *) fastEquipmentB;
+- (NSString *) fastEquipmentC;
+- (NSString *) fastEquipmentD;
 - (void) setFastEquipmentA:(NSString *)eqKey;
 - (void) setFastEquipmentB:(NSString *)eqKey;
+- (void) setFastEquipmentC:(NSString *)eqKey;
+- (void) setFastEquipmentD:(NSString *)eqKey;
 
 - (OOCreditsQuantity) adjustPriceByScriptForEqKey:(NSString *)eqKey withCurrent:(OOCreditsQuantity)price;
 
diff --git a/src/Core/Entities/PlayerEntity.m b/src/Core/Entities/PlayerEntity.m
index af4a7c63..3f3000d7 100644
--- a/src/Core/Entities/PlayerEntity.m
+++ b/src/Core/Entities/PlayerEntity.m
@@ -1034,7 +1034,9 @@ NSComparisonResult marketSorterByMassUnit(id a, id b, void *market);
 	
 	[result setObject:[self fastEquipmentA] forKey:@"primed_equipment_a"];
 	[result setObject:[self fastEquipmentB] forKey:@"primed_equipment_b"];
-
+	[result setObject:[self fastEquipmentC] forKey:@"primed_equipment_c"];
+	[result setObject:[self fastEquipmentD] forKey:@"primed_equipment_d"];
+	
 	// roles
 	[result setObject:roleWeights forKey:@"role_weights"];
 
@@ -1383,7 +1385,9 @@ NSComparisonResult marketSorterByMassUnit(id a, id b, void *market);
 	
 	[self setFastEquipmentA:[dict oo_stringForKey:@"primed_equipment_a" defaultValue:@"EQ_CLOAKING_DEVICE"]];
 	[self setFastEquipmentB:[dict oo_stringForKey:@"primed_equipment_b" defaultValue:@"EQ_ENERGY_BOMB"]]; // even though there isn't one, for compatibility.
-
+	[self setFastEquipmentC:[dict oo_stringForKey:@"primed_equipment_c" defaultValue:@""]];
+	[self setFastEquipmentD:[dict oo_stringForKey:@"primed_equipment_d" defaultValue:@""]];
+	
 	if ([self hasEquipmentItemProviding:@"EQ_ADVANCED_COMPASS"])  compassMode = COMPASS_MODE_PLANET;
 	else  compassMode = COMPASS_MODE_BASIC;
 	DESTROY(compassTarget);
@@ -2114,7 +2118,9 @@ NSComparisonResult marketSorterByMassUnit(id a, id b, void *market);
 	primedEquipment = 0;
 	[self setFastEquipmentA:@"EQ_CLOAKING_DEVICE"];
 	[self setFastEquipmentB:@"EQ_ENERGY_BOMB"]; // for compatibility purposes
-
+	[self setFastEquipmentC:@""];
+	[self setFastEquipmentD:@""];
+	
 	[self setActiveMissile:0];
 	for (i = 0; i < missiles; i++)
 	{
@@ -2361,6 +2367,8 @@ NSComparisonResult marketSorterByMassUnit(id a, id b, void *market);
 	
 	DESTROY(_fastEquipmentA);
 	DESTROY(_fastEquipmentB);
+	DESTROY(_fastEquipmentC);
+	DESTROY(_fastEquipmentD);
 
 	DESTROY(eqScripts);
 	DESTROY(worldScripts);
@@ -8131,6 +8139,18 @@ NSComparisonResult marketSorterByMassUnit(id a, id b, void *market);
 }
 
 
+- (NSString *) fastEquipmentC
+{
+	return _fastEquipmentC;
+}
+
+
+- (NSString *) fastEquipmentD
+{
+	return _fastEquipmentD;
+}
+
+
 - (void) setFastEquipmentA:(NSString *)eqKey
 {
 	[_fastEquipmentA release];
@@ -8145,6 +8165,20 @@ NSComparisonResult marketSorterByMassUnit(id a, id b, void *market);
 }
 
 
+- (void) setFastEquipmentC:(NSString *)eqKey
+{
+	[_fastEquipmentC release];
+	_fastEquipmentC = [eqKey copy];
+}
+
+
+- (void) setFastEquipmentD:(NSString *)eqKey
+{
+	[_fastEquipmentD release];
+	_fastEquipmentD = [eqKey copy];
+}
+
+
 - (OOEquipmentType *) weaponTypeForFacing:(OOWeaponFacing)facing strict:(BOOL)strict
 {
 	OOWeaponType weaponType = nil;
@@ -9930,6 +9964,7 @@ static NSString *last_outfitting_key=nil;
 		 @"",@"",@"",
 		 @"key_prime_equipment",@"key_activate_equipment",@"key_mode_equipment",
 		 @"key_fastactivate_equipment_a",@"key_fastactivate_equipment_b",@"", //
+		 @"key_fastactivate_equipment_c",@"key_fastactivate_equipment_d",@"", //
 #if OO_FOV_INFLIGHT_CONTROL_ENABLED
 		@"key_inc_field_of_view",@"key_dec_field_of_view",@"",
 #else
@@ -13239,6 +13274,8 @@ else _dockTarget = NO_TARGET;
 	key_mode_equipment &&
 	key_fastactivate_equipment_a &&
 	key_fastactivate_equipment_b &&
+	key_fastactivate_equipment_c &&
+	key_fastactivate_equipment_d &&
 	key_target_missile &&
 	key_untarget_missile &&
 	key_target_incoming_missile &&
diff --git a/src/Core/Entities/PlayerEntityControls.m b/src/Core/Entities/PlayerEntityControls.m
index 2bb82202..2fde811d 100644
--- a/src/Core/Entities/PlayerEntityControls.m
+++ b/src/Core/Entities/PlayerEntityControls.m
@@ -81,6 +81,8 @@ static BOOL				activate_equipment_pressed;
 static BOOL				mode_equipment_pressed;
 static BOOL				fastactivate_a_pressed;
 static BOOL				fastactivate_b_pressed;
+static BOOL				fastactivate_c_pressed;
+static BOOL				fastactivate_d_pressed;
 static BOOL				next_missile_pressed;
 static BOOL				fire_missile_pressed;
 static BOOL				target_missile_pressed;
@@ -280,6 +282,8 @@ static NSTimeInterval	time_last_frame;
 	LOAD_KEY_SETTING(key_mode_equipment,		'b'			);
 	LOAD_KEY_SETTING_ALIAS(key_fastactivate_equipment_a, key_cloaking_device,		'0'			);
 	LOAD_KEY_SETTING_ALIAS(key_fastactivate_equipment_b, key_energy_bomb,			'\t'		);
+	LOAD_KEY_SETTING(key_fastactivate_equipment_c,		'['	);
+	LOAD_KEY_SETTING(key_fastactivate_equipment_d,		']'	);
 	
 	LOAD_KEY_SETTING(key_target_missile,		't'			);
 	LOAD_KEY_SETTING(key_untarget_missile,		'u'			);
@@ -1162,7 +1166,16 @@ static NSTimeInterval	time_last_frame;
 				{
 					if (!fastactivate_a_pressed)
 					{
-						[self activatePrimableEquipment:[self eqScriptIndexForKey:[self fastEquipmentA]] withMode:OOPRIMEDEQUIP_ACTIVATED];
+						// if Ctrl is held down at the same time as the fast activation key,
+						// run the mode() function inside the equipment's script instead of activate()
+						if ([gameView isCtrlDown])
+						{
+							[self activatePrimableEquipment:[self eqScriptIndexForKey:[self fastEquipmentA]] withMode:OOPRIMEDEQUIP_MODE];
+						}
+						else
+						{
+							[self activatePrimableEquipment:[self eqScriptIndexForKey:[self fastEquipmentA]] withMode:OOPRIMEDEQUIP_ACTIVATED];
+						}
 					}
 					fastactivate_a_pressed = YES;
 				}
@@ -1173,12 +1186,60 @@ static NSTimeInterval	time_last_frame;
 				{
 					if (!fastactivate_b_pressed)
 					{
-						[self activatePrimableEquipment:[self eqScriptIndexForKey:[self fastEquipmentB]] withMode:OOPRIMEDEQUIP_ACTIVATED];
+						// if Ctrl is held down at the same time as the fast activation key,
+						// run the mode() function inside the equipment's script instead of activate()
+						if ([gameView isCtrlDown])
+						{
+							[self activatePrimableEquipment:[self eqScriptIndexForKey:[self fastEquipmentB]] withMode:OOPRIMEDEQUIP_MODE];
+						}
+						else
+						{
+							[self activatePrimableEquipment:[self eqScriptIndexForKey:[self fastEquipmentB]] withMode:OOPRIMEDEQUIP_ACTIVATED];
+						}
 					}
 					fastactivate_b_pressed = YES;
 				}
 				else fastactivate_b_pressed = NO;
 
+				exceptionContext = @"fast equipment C";
+				if ([gameView isDown:key_fastactivate_equipment_c] || joyButtonState[BUTTON_FASTACTIVATE_C])
+				{
+					if (!fastactivate_c_pressed)
+					{
+						// if Ctrl is held down at the same time as the fast activation key,
+						// run the mode() function inside the equipment's script instead of activate()
+						if ([gameView isCtrlDown])
+						{
+							[self activatePrimableEquipment:[self eqScriptIndexForKey:[self fastEquipmentC]] withMode:OOPRIMEDEQUIP_MODE];
+						}
+						else
+						{
+							[self activatePrimableEquipment:[self eqScriptIndexForKey:[self fastEquipmentC]] withMode:OOPRIMEDEQUIP_ACTIVATED];
+						}
+					}
+					fastactivate_c_pressed = YES;
+				}
+				else fastactivate_c_pressed = NO;
+
+				exceptionContext = @"fast equipment D";
+				if ([gameView isDown:key_fastactivate_equipment_d] || joyButtonState[BUTTON_FASTACTIVATE_D])
+				{
+					if (!fastactivate_d_pressed)
+					{
+						// if Ctrl is held down at the same time as the fast activation key,
+						// run the mode() function inside the equipment's script instead of activate()
+						if ([gameView isCtrlDown])
+						{
+							[self activatePrimableEquipment:[self eqScriptIndexForKey:[self fastEquipmentD]] withMode:OOPRIMEDEQUIP_MODE];
+						}
+						else
+						{
+							[self activatePrimableEquipment:[self eqScriptIndexForKey:[self fastEquipmentD]] withMode:OOPRIMEDEQUIP_ACTIVATED];
+						}
+					}
+					fastactivate_d_pressed = YES;
+				}
+				else fastactivate_d_pressed = NO;
 
 				exceptionContext = @"incoming missile T";
 				// target nearest incoming missile 'T' - useful for quickly giving a missile target to turrets
diff --git a/src/Core/Entities/PlayerEntityStickMapper.m b/src/Core/Entities/PlayerEntityStickMapper.m
index b9236370..ff39d2a2 100644
--- a/src/Core/Entities/PlayerEntityStickMapper.m
+++ b/src/Core/Entities/PlayerEntityStickMapper.m
@@ -565,6 +565,16 @@ MA 02110-1301, USA.
 				  allowable:HW_BUTTON
 					 axisfn:STICK_NOFUNCTION
 					  butfn:BUTTON_ENERGYBOMB]];
+	[funcList addObject:
+	 [self makeStickGuiDict:DESC(@"stickmapper-fastactivate-c")
+				  allowable:HW_BUTTON
+					 axisfn:STICK_NOFUNCTION
+					  butfn:BUTTON_FASTACTIVATE_C]];
+	[funcList addObject:
+	 [self makeStickGuiDict:DESC(@"stickmapper-fastactivate-d")
+				  allowable:HW_BUTTON
+					 axisfn:STICK_NOFUNCTION
+					  butfn:BUTTON_FASTACTIVATE_D]];
 	[funcList addObject:
 	 [self makeStickGuiDict:DESC(@"stickmapper-ECM")
 				  allowable:HW_BUTTON
diff --git a/src/Core/OOJoystickManager.h b/src/Core/OOJoystickManager.h
index 063ecefc..1e802ebb 100644
--- a/src/Core/OOJoystickManager.h
+++ b/src/Core/OOJoystickManager.h
@@ -99,6 +99,8 @@ enum {
 	BUTTON_DEC_FIELD_OF_VIEW,
 #endif
 	BUTTON_DOCKINGCLEARANCE,
+	BUTTON_FASTACTIVATE_C,
+	BUTTON_FASTACTIVATE_D,
 	BUTTON_end
 };
 
diff --git a/src/Core/Scripting/OOJSPlayerShip.m b/src/Core/Scripting/OOJSPlayerShip.m
index 95c696b4..6a5a4379 100644
--- a/src/Core/Scripting/OOJSPlayerShip.m
+++ b/src/Core/Scripting/OOJSPlayerShip.m
@@ -118,6 +118,8 @@ enum
 	kPlayerShip_dockedStation,					// docked station, entity, read-only
 	kPlayerShip_fastEquipmentA,					// fast equipment A, string, read/write
 	kPlayerShip_fastEquipmentB,					// fast equipment B, string, read/write
+	kPlayerShip_fastEquipmentC,					// fast equipment C, string, read/write
+	kPlayerShip_fastEquipmentD,					// fast equipment D, string, read/write
 	kPlayerShip_forwardShield,					// forward shield charge level, nonnegative float, read/write
 	kPlayerShip_forwardShieldRechargeRate,		// forward shield recharge rate, positive float, read-only
 	kPlayerShip_fuelLeakRate,					// fuel leak rate, float, read/write
@@ -187,6 +189,8 @@ static JSPropertySpec sPlayerShipProperties[] =
 	{ "dockedStation",					kPlayerShip_dockedStation,					OOJS_PROP_READONLY_CB },
 	{ "fastEquipmentA",					kPlayerShip_fastEquipmentA,					OOJS_PROP_READWRITE_CB },
 	{ "fastEquipmentB",					kPlayerShip_fastEquipmentB,					OOJS_PROP_READWRITE_CB },
+	{ "fastEquipmentC",					kPlayerShip_fastEquipmentC,					OOJS_PROP_READWRITE_CB },
+	{ "fastEquipmentD",					kPlayerShip_fastEquipmentD,					OOJS_PROP_READWRITE_CB },
 	{ "forwardShield",					kPlayerShip_forwardShield,					OOJS_PROP_READWRITE_CB },
 	{ "forwardShieldRechargeRate",		kPlayerShip_forwardShieldRechargeRate,		OOJS_PROP_READWRITE_CB },
 	{ "fuelLeakRate",					kPlayerShip_fuelLeakRate,					OOJS_PROP_READWRITE_CB },
@@ -411,6 +415,14 @@ static JSBool PlayerShipGetProperty(JSContext *context, JSObject *this, jsid pro
 			result = [player fastEquipmentB];
 			break;
 
+		case kPlayerShip_fastEquipmentC:
+			result = [player fastEquipmentC];
+			break;
+
+		case kPlayerShip_fastEquipmentD:
+			result = [player fastEquipmentD];
+			break;
+
 		case kPlayerShip_primedEquipment:
 			result = [player currentPrimedEquipment];
 			break;
@@ -788,6 +800,24 @@ static JSBool PlayerShipSetProperty(JSContext *context, JSObject *this, jsid pro
 			}
 			break;
 
+		case kPlayerShip_fastEquipmentC:
+			sValue = OOStringFromJSValue(context, *value);
+			if (sValue != nil)
+			{
+				[player setFastEquipmentC:sValue];
+				return YES;
+			}
+			break;
+
+		case kPlayerShip_fastEquipmentD:
+			sValue = OOStringFromJSValue(context, *value);
+			if (sValue != nil)
+			{
+				[player setFastEquipmentD:sValue];
+				return YES;
+			}
+			break;
+
 		case kPlayerShip_primedEquipment:
 			sValue = OOStringFromJSValue(context, *value);
 			if (sValue != nil)
For any other users of cag's scripts, here is the revised version of oolite-primable-equipment-manager.js which goes in Resources\Scripts (my code above includes a modified version of the core script that is functionally identical but doesn't include cag's optimizations):

Code: Select all

/*

oolite-primable-equipment-manager.js

Allocate primable equipment to the two 'fast activate' buttons.


Oolite
Copyright © 2004-2013 Giles C Williams and contributors

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
MA 02110-1301, USA.

*/


/*jslint white: true, undef: true, eqeqeq: true, bitwise: true, regexp: true, newcap: true, immed: true */
/*global player*/



this.name			= "oolite-primable-equipment-register";
this.author			= "cim";
this.copyright		= "© 2008-2013 the Oolite team.";

(function(){
"use strict";

this.startUpComplete = this.shipDockedWithStation = this.playerBoughtNewShip = function shipDockedWithStation()
{
	this._initialiseInterface();
}


this.playerBoughtEquipment = function playerBoughtEquipment(eqkey)
{
	this._initialiseInterface();
	this._updatePrimableEquipmentSettings(eqkey,false);
}


this._updatePrimableEquipmentSettings = function _updatePrimableEquipmentSettings(eqkey,quiet)
{
	var that = _updatePrimableEquipmentSettings;
	var msg = (that.msg = that.msg || {});

	/* If the player has gained some equipment which is recommended
	 * for fast activation, and the activation slot has nothing in it,
	 * assign to that slot. */
	var info = EquipmentInfo.infoForKey(eqkey);
	
	var fastEq_status = player.ship.equipmentStatus(player.ship.fastEquipmentA);
	if (info.fastAffinityDefensive && fastEq_status !== "EQUIPMENT_OK" && fastEq_status !== "EQUIPMENT_DAMAGED")
	{
		// no installed equipment in fast slot A, so assign this
		player.ship.fastEquipmentA = eqkey;
		if (!quiet)
		{
			msg["oolite-primable-equipment"] = info.name;
			msg["oolite-primable-slot"] = expandDescription("[oolite-primablemanager-slot-defensive]");
			player.consoleMessage(expandDescription("[oolite-primablemanager-notify-assign]",msg),7.5);
			player.consoleMessage(expandDescription("[oolite-primablemanager-notify-setup]"),7.5);
		}
		return;
	} 
	fastEq_status = player.ship.equipmentStatus(player.ship.fastEquipmentB);
	if (info.fastAffinityOffensive && fastEq_status !== "EQUIPMENT_OK" && fastEq_status !== "EQUIPMENT_DAMAGED")
	{
		// no installed equipment in fast slot B, so assign this
		player.ship.fastEquipmentB = eqkey;
		if (!quiet)
		{
			msg["oolite-primable-equipment"] = info.name;
			msg["oolite-primable-slot"] = expandDescription("[oolite-primablemanager-slot-offensive]");
			player.consoleMessage(expandDescription("[oolite-primablemanager-notify-assign]",msg),7.5);
			player.consoleMessage(expandDescription("[oolite-primablemanager-notify-setup]"),7.5);
		}
	}

}


this._initialiseInterface = function _initialiseInterface()
{
	var that = _initialiseInterface;
	var screen = (that.screen = that.screen || {});

	if (player.ship.dockedStation)
	{
		if (this._equipmentWithScripts().length > 0)
		{
			screen.title = expandMissionText("oolite-primablemanager-interface-title");
			screen.category = expandMissionText("oolite-primablemanager-interface-category");
			screen.summary = expandMissionText("oolite-primablemanager-interface-summary");
			screen.callback = this._configurePrimableEquipment.bind(this);
			player.ship.dockedStation.setInterface("oolite-primable-equipment-manager",screen);
		}
		else
			player.ship.dockedStation.setInterface("oolite-primable-equipment-manager",null);
	}
}


this._equipmentWithScripts = function _equipmentWithScripts()
{
	var that = _equipmentWithScripts;
	var result = (that.result = that.result || []);

	result.length = 0; // re-use array
	var equipment = player.ship.equipment;
	var piece, len = equipment.length;
	for (var i=0;i<len;i++)
	{
		piece = equipment[i];
		if (piece.scriptName !== "")
		{
			result.push(piece);
		}
	}
	return result;
}


this._nameEquipment = function _nameEquipment(key) 
{
	var equipment = this._equipmentWithScripts();
	var piece, len = equipment.length;
	for (var i=0;i<len;i++)
	{
		piece = equipment[i];
		if (piece.equipmentKey == key)
		{
			return piece.name;
		}
	}	
	return expandMissionText("oolite-primablemanager-select-none");
}

this.$choice_pool = [];
this._get_choice = function _get_choice(name) {
    if (this.$choice_pool.length > 0) {
        let choice = this.$choice_pool.pop();
		choice.text = name;
        return choice;
    }
    return {text: name};
}.bind(this)
	
this._free_choice = function _free_choice(c) {
    if (c !== null) {
		c.text = null;
		c.color = null;
        this.$choice_pool.push(c);
    }
}.bind(this)


this._equipmentChoices = function _equipmentChoices(current) {
	var that = _equipmentChoices;
	var _get_choice = (that._get_choice = that._get_choice || this._get_choice);
	var _free_choice = (that._free_choice = that._free_choice || this._free_choice);
	var choices = (that.choices = that.choices || {});

	for (var x in choices) // re-use object
		if (choices.hasOwnProperty(x)) _free_choice(choices[x]);
	var equipment = this._equipmentWithScripts();
	var len = equipment.length;
	var chosen = false;
	var choice, piece;
	for (var i=0;i<len;i++)
	{
		piece = equipment[i];
		choice = _get_choice(piece.name);
		if (piece.equipmentKey == current)
		{
			choice.color = "greenColor";
			choice.text += " "+expandMissionText("oolite-primablemanager-selected-text");
			chosen = true;
		}
		choices[piece.equipmentKey] = choice;
	}
	choice = _get_choice(expandMissionText("oolite-primablemanager-select-none"));
	if (!chosen)
	{
		choice.color = "greenColor";
		choice.text += " "+expandMissionText("oolite-primablemanager-selected-text");
	}
	choices.ZZZZZZ_OOLITE_EQ_NONE = choice;

	return choices;
}


this._initialChoice = function _initialChoice(key)
{
	var status = player.ship.equipmentStatus(key);
	if (status === "EQUIPMENT_OK" || status === "EQUIPMENT_DAMAGED")
	{
		return key;
	}
	return "ZZZZZZ_OOLITE_EQ_NONE";
}


this._configurePrimableEquipment = function _configurePrimableEquipment()
{
	var that = _configurePrimableEquipment;
	var stage1 = (that.stage1 = that.stage1 || {});

	stage1.titleKey = "oolite-primablemanager-page1-title";
	stage1.messageKey = "oolite-primablemanager-setup-text";
	stage1.choices = this._equipmentChoices(player.ship.fastEquipmentA);
	stage1.initialChoicesKey = this._initialChoice(player.ship.fastEquipmentA);
	stage1.exitScreen = "GUI_SCREEN_INTERFACES";
	stage1.screenID = "oolite-primablemanager";
	mission.runScreen(stage1,this._configureStage2.bind(this));
}


this._configureStage2 = function _configureStage2(choice)
{
	var that = _configureStage2;
	var stage2 = (that.stage2 = that.stage2 || {});

	if (choice !== "") 
	{
		player.ship.fastEquipmentA = choice;
	}
	
	stage2.titleKey = "oolite-primablemanager-page2-title";
	stage2.messageKey = "oolite-primablemanager-setup-text";
	stage2.choices = this._equipmentChoices(player.ship.fastEquipmentB);
	stage2.initialChoicesKey = this._initialChoice(player.ship.fastEquipmentB);
	stage2.exitScreen = "GUI_SCREEN_INTERFACES";
	stage2.screenID = "oolite-primablemanager";
	mission.runScreen(stage2,this._configureStage3.bind(this));
}

this._configureStage3 = function _configureStage3(choice)
{
	var that = _configureStage3;
	var stage3 = (that.stage3 = that.stage3 || {});

	if (choice !== "") 
	{
		player.ship.fastEquipmentB = choice;
	}
	
	stage3.titleKey = "oolite-primablemanager-page3-title";
	stage3.messageKey = "oolite-primablemanager-setup-text";
	stage3.choices = this._equipmentChoices(player.ship.fastEquipmentC);
	stage3.initialChoicesKey = this._initialChoice(player.ship.fastEquipmentC);
	stage3.exitScreen = "GUI_SCREEN_INTERFACES";
	stage3.screenID = "oolite-primablemanager";
	mission.runScreen(stage3,this._configureStage4.bind(this));
}

this._configureStage4 = function _configureStage4(choice)
{
	var that = _configureStage4;
	var stage4 = (that.stage4 = that.stage4 || {});

	if (choice !== "") 
	{
		player.ship.fastEquipmentC = choice;
	}
	
	stage4.titleKey = "oolite-primablemanager-page4-title";
	stage4.messageKey = "oolite-primablemanager-setup-text";
	stage4.choices = this._equipmentChoices(player.ship.fastEquipmentD);
	stage4.initialChoicesKey = this._initialChoice(player.ship.fastEquipmentD);
	stage4.exitScreen = "GUI_SCREEN_INTERFACES";
	stage4.screenID = "oolite-primablemanager";
	mission.runScreen(stage4,this._configureStage5.bind(this));
}

this._configureStage5 = function _configureStage5(choice)
{
	var that = _configureStage5;
	var stage5 = (that.stage5 = that.stage5 || {});
	var final_msg = (that.final_msg = that.final_msg || {});

	if (choice !== "") 
	{
		player.ship.fastEquipmentD = choice;
	}
	final_msg["oolite-primable-a"] = this._nameEquipment(player.ship.fastEquipmentA);
	final_msg["oolite-primable-b"] = this._nameEquipment(player.ship.fastEquipmentB);
	final_msg["oolite-primable-c"] = this._nameEquipment(player.ship.fastEquipmentC);
	final_msg["oolite-primable-d"] = this._nameEquipment(player.ship.fastEquipmentD);
	var message = expandMissionText("oolite-primablemanager-complete",final_msg);

	stage5.titleKey = "oolite-primablemanager-page5-title";
	stage5.message = message;
	stage5.exitScreen = "GUI_SCREEN_INTERFACES";
	stage5.screenID = "oolite-primablemanager";
	mission.runScreen(stage5);
}

}).call(this);
Post Reply