Index: code/hud/hudets.h
===================================================================
--- code/hud/hudets.h	(revision 9961)
+++ code/hud/hudets.h	(working copy)
@@ -16,6 +16,25 @@
 
 struct object;
 
+#define ENERGY_DIVERT_DELTA				0.2f	// percentage of energy transferred in a shield->weapon or weapon->shield energy transfer
+#define INTIAL_SHIELD_RECHARGE_INDEX	4		// default shield charge rate (index in Energy_levels[])
+#define INTIAL_WEAPON_RECHARGE_INDEX	4		// default weapon charge rate (index in Energy_levels[])
+#define INTIAL_ENGINE_RECHARGE_INDEX	4		// default engine charge rate (index in Energy_levels[])
+
+#define NUM_ENERGY_LEVELS	13
+#define MAX_ENERGY_INDEX	(NUM_ENERGY_LEVELS - 1)
+
+#define AI_MODIFY_ETS_INTERVAL 500	// time between ets modifications for ai's (in milliseconds)
+
+#define ZERO_INDEX			0
+#define ONE_THIRD_INDEX		4
+#define ONE_HALF_INDEX		6
+#define ALL_INDEX			12
+
+#define HAS_ENGINES			(1<<0)
+#define HAS_SHIELDS			(1<<1)
+#define HAS_WEAPONS			(1<<2)
+
 #define	ETS_RECHARGE_RATE	4.0f			//	Recharge this percent of total shields/second
 
 const int num_retail_ets_gauges = 3;
@@ -37,6 +56,8 @@
 void transfer_energy_to_weapons(object* obj);
 
 float ets_get_max_speed(object* objp, float engine_energy);
+void sanity_check_ets_inputs(int (&ets_indexes)[num_retail_ets_gauges]);
+bool validate_ship_ets_indxes(const int &ship_idx, int (&ets_indexes)[num_retail_ets_gauges]);
 
 class HudGaugeEts: public HudGauge // HUD_ETS_GAUGE
 {
Index: code/hud/hudets.cpp
===================================================================
--- code/hud/hudets.cpp	(revision 9961)
+++ code/hud/hudets.cpp	(working copy)
@@ -23,33 +23,10 @@
 #include "object/object.h"
 #include "object/objectshield.h"
 #include "ship/subsysdamage.h"
-	
 
-#define ENERGY_DIVERT_DELTA				0.2f	// percentage of energy transferred in a shield->weapon or weapon->shield energy transfer
-#define INTIAL_SHIELD_RECHARGE_INDEX	4		// default shield charge rate (index in Energy_levels[])
-#define INTIAL_WEAPON_RECHARGE_INDEX	4		// default weapon charge rate (index in Energy_levels[])
-#define INTIAL_ENGINE_RECHARGE_INDEX	4		// default engine charge rate (index in Energy_levels[])
-
-//#define MAX_SHIELD_REGEN_PER_SECOND		0.02f	// max percent/100 of shield energy regenerated per second
-//#define MAX_WEAPON_REGEN_PER_SECOND		0.04f	// max percent/100 of weapon energy regenerated per second
-
-#define NUM_ENERGY_LEVELS	13		
-#define MAX_ENERGY_INDEX	(NUM_ENERGY_LEVELS - 1)
 float Energy_levels[NUM_ENERGY_LEVELS] = {0.0f,  0.0833f, 0.167f, 0.25f, 0.333f, 0.417f, 0.5f, 0.583f, 0.667f, 0.75f, 0.833f, 0.9167f, 1.0f};
-
-#define AI_MODIFY_ETS_INTERVAL 500	// time between ets modifications for ai's (in milliseconds)
-
 int Weapon_energy_cheat = 0;
 
-#define ZERO_INDEX			0
-#define ONE_THIRD_INDEX		4
-#define ONE_HALF_INDEX		6
-#define ALL_INDEX				12
-
-#define HAS_ENGINES			(1<<0)
-#define HAS_SHIELDS			(1<<1)
-#define HAS_WEAPONS			(1<<2)
-
 // -------------------------------------------------------------------------------------------------
 // ets_init_ship() is called by a ship when it is created (effectively, for every ship at the start
 // of a mission).  This will set the default charge rates for the different systems and initialize
@@ -669,6 +646,138 @@
 	transfer_energy_weapon_common(obj, shield_get_strength(obj), ship_p->weapon_energy, &ship_p->target_shields_delta, &ship_p->target_weapon_energy_delta, sinfo_p->max_weapon_reserve, 1.0f);
 }
 
+/**
+ * decrease one ets index to zero & adjust others up
+ */
+void zero_one_ets (int *reduce, int *add1, int *add2)
+{
+	int *tmp;
+	// add to the smallest index 1st
+	if (*add1 > *add2) {
+		tmp = add1;
+		add1 = add2;
+		add2 = tmp;
+	}
+	while (*reduce > ZERO_INDEX) {
+		if (*add1 < ALL_INDEX) {
+			++*add1;
+			--*reduce;
+		}
+
+		if (*reduce <= ZERO_INDEX) {
+			break;
+		}
+
+		if (*add2 < ALL_INDEX) {
+			++*add2;
+			--*reduce;
+		}
+	}
+}
+
+/**
+ * ensure input ETS indexs are valid.
+ * If not, "fix" them by moving outliers towards the middle index
+ */
+ void sanity_check_ets_inputs(int (&ets_indexes)[num_retail_ets_gauges])
+ {
+	int i;
+	int ets_delta = MAX_ENERGY_INDEX - ets_indexes[ENGINES] - ets_indexes[SHIELDS] - ets_indexes[WEAPONS];
+	if ( ets_delta != 0 ) {
+		if ( ets_delta > 0) { // add to lowest indexes
+			while ( ets_delta != 0 ) {
+				int lowest_val = MAX_ENERGY_INDEX;
+				int lowest_idx = 0;
+
+				for (i = 0; i < num_retail_ets_gauges; ++i) {
+					if (ets_indexes[i] <= lowest_val ) {
+						lowest_val = ets_indexes[i];
+						lowest_idx = i;
+					}
+				}
+				++ets_indexes[lowest_idx];
+				--ets_delta;
+			}
+		} else { // remove from highest indexes
+			while ( ets_delta != 0 ) {
+				int highest_val = 0;
+				int highest_idx = 0;
+
+				for (i = 0; i < num_retail_ets_gauges; ++i) {
+					if (ets_indexes[i] >= highest_val ) {
+						highest_val = ets_indexes[i];
+						highest_idx = i;
+					}
+				}
+				--ets_indexes[highest_idx];
+				++ets_delta;
+			}
+		}
+	}
+ }
+
+ /**
+  * adjust input ETS indexes to handle missing systems on the target ship
+  * return true if indexes are valid to be set
+  */
+bool validate_ship_ets_indxes(const int &ship_idx, int (&ets_indexes)[num_retail_ets_gauges])
+{
+	if (ship_idx < 0) {
+		return false;
+	}
+	if (Ships[ship_idx].objnum < 0) {
+		return false;
+	}
+	ship *ship_p = &Ships[ship_idx];
+
+	if (ship_p->flags2 & SF2_NO_ETS)
+		return false;
+
+	// handle ships that are missing parts of the ETS
+	int ship_properties = 0;
+	if (ship_has_energy_weapons(ship_p)) {
+		ship_properties |= HAS_WEAPONS;
+	}
+
+	if (!(Objects[ship_p->objnum].flags & OF_NO_SHIELDS)) {
+		ship_properties |= HAS_SHIELDS;
+	}
+
+	if (ship_has_engine_power(ship_p)) {
+		ship_properties |= HAS_ENGINES;
+	}
+
+	switch ( ship_properties ) {
+		case HAS_ENGINES | HAS_WEAPONS | HAS_SHIELDS:
+			// all present, don't change ets indexes
+			break;
+
+		case HAS_ENGINES | HAS_SHIELDS:
+			zero_one_ets(&ets_indexes[WEAPONS], &ets_indexes[ENGINES], &ets_indexes[SHIELDS]);
+			break;
+
+		case HAS_WEAPONS | HAS_SHIELDS:
+			zero_one_ets(&ets_indexes[ENGINES], &ets_indexes[SHIELDS], &ets_indexes[WEAPONS]);
+			break;
+
+		case HAS_ENGINES | HAS_WEAPONS:
+			zero_one_ets(&ets_indexes[SHIELDS], &ets_indexes[ENGINES], &ets_indexes[WEAPONS]);
+			break;
+
+		case HAS_ENGINES:
+		case HAS_SHIELDS:
+		case HAS_WEAPONS:
+			// can't change anything if only one is active on this ship
+			return false;
+			break;
+
+		default:
+			Error(LOCATION, "Encountered a ship (%s) with a broken ETS", ship_p->ship_name);
+			break;
+	}
+	return true;
+}
+
 HudGaugeEts::HudGaugeEts():
 HudGauge(HUD_OBJECT_ETS_ENGINES, HUD_ETS_GAUGE, false, false, (VM_EXTERNAL | VM_DEAD_VIEW | VM_WARP_CHASE | VM_PADLOCK_ANY | VM_OTHER_SHIP), 255, 255, 255),
 System_type(0)
Index: code/parse/sexp.cpp
===================================================================
--- code/parse/sexp.cpp	(revision 9961)
+++ code/parse/sexp.cpp	(working copy)
@@ -421,6 +421,8 @@
 	{ "never-warp",						OP_WARP_NEVER,							1,	INT_MAX,	SEXP_ACTION_OPERATOR,	},
 	{ "allow-warp",						OP_WARP_ALLOWED,						1,	INT_MAX,	SEXP_ACTION_OPERATOR,	},
 	{ "special-warpout-name",			OP_SET_SPECIAL_WARPOUT_NAME,			2,	2,			SEXP_ACTION_OPERATOR,	},
+	{ "get-ets-value",					OP_GET_ETS_VALUE,						2,	2,			SEXP_ACTION_OPERATOR,	},	// niffiwan
+	{ "set-ets-values",					OP_SET_ETS_VALUES,						4,	INT_MAX,	SEXP_ACTION_OPERATOR,	},	// niffiwan
 
 	//Subsystems and Health Sub-Category
 	{ "ship-invulnerable",				OP_SHIP_INVULNERABLE,					1,	INT_MAX,	SEXP_ACTION_OPERATOR,	},
@@ -15101,6 +15103,90 @@
 	return (int)(100.0f * Energy_levels[Ships[sindex].weapon_recharge_index]);
 }
 
+/**
+ * retrieve one ETS index from a ship
+ */
+int sexp_get_ets_value(int node)
+{
+	int sindex;
+	SCP_string ets_type;
+
+	ets_type = CTEXT(node);
+	node = CDR(node);
+
+	sindex = ship_name_lookup(CTEXT(node));
+	if (sindex < 0) {
+		return SEXP_FALSE;
+	}
+	if (Ships[sindex].objnum < 0) {
+		return SEXP_FALSE;
+	}
+
+	if (!stricmp(ets_type.c_str(), "engine")) {
+		return Ships[sindex].engine_recharge_index;
+	} else if (!stricmp(ets_type.c_str(), "shield")) {
+		return Ships[sindex].shield_recharge_index;
+	} else if (!stricmp(ets_type.c_str(), "weapon")) {
+		return Ships[sindex].weapon_recharge_index;
+	} else {
+		return SEXP_FALSE;
+	}
+}
+
+/**
+ * set all ETS indexes for one or more ships
+ */
+void sexp_set_ets_values(int node)
+{
+	int sindex;
+	int ets_idx[num_retail_ets_gauges];
+
+	ets_idx[ENGINES] = eval_num(node);
+	node = CDR(node);
+	ets_idx[SHIELDS] = eval_num(node);
+	node = CDR(node);
+	ets_idx[WEAPONS] = eval_num(node);
+	node = CDR(node);
+
+	// sanity check inputs
+	sanity_check_ets_inputs(ets_idx);
+
+	multi_start_callback();
+
+	// apply ETS settings to specified ships
+	for ( ; node != -1; node = CDR(node)) {
+		sindex = ship_name_lookup(CTEXT(node));
+
+		if (sindex >= 0 && validate_ship_ets_indxes(sindex, ets_idx)) {
+			Ships[sindex].engine_recharge_index = ets_idx[ENGINES];
+			Ships[sindex].shield_recharge_index = ets_idx[SHIELDS];
+			Ships[sindex].weapon_recharge_index = ets_idx[WEAPONS];
+
+			multi_send_ship(sindex);
+			multi_send_int(ets_idx[ENGINES]);
+			multi_send_int(ets_idx[SHIELDS]);
+			multi_send_int(ets_idx[WEAPONS]);
+		}
+	}
+	multi_end_callback();
+}
+
+void multi_sexp_set_ets_values()
+{
+	int sindex;
+	int ets_idx[num_retail_ets_gauges];
+
+	while (multi_get_ship(sindex)) {
+		multi_get_int(ets_idx[ENGINES]);
+		multi_get_int(ets_idx[SHIELDS]);
+		multi_get_int(ets_idx[WEAPONS]);
+
+		Ships[sindex].engine_recharge_index = ets_idx[ENGINES];
+		Ships[sindex].shield_recharge_index = ets_idx[SHIELDS];
+		Ships[sindex].weapon_recharge_index = ets_idx[WEAPONS];
+	}
+}
+
 int sexp_shield_quad_low(int node)
 {
 	int sindex, idx;	
@@ -23188,6 +23274,15 @@
 				sexp_val = sexp_weapon_recharge_pct(node);
 				break;
 
+			case OP_GET_ETS_VALUE:
+				sexp_val = sexp_get_ets_value(node);
+				break;
+
+			case OP_SET_ETS_VALUES:
+				sexp_val = SEXP_TRUE;
+				sexp_set_ets_values(node);
+				break;
+
 			case OP_SHIELD_QUAD_LOW:
 				sexp_val = sexp_shield_quad_low(node);
 				break;
@@ -24045,6 +24140,10 @@
 				sexp_reset_time_compression();
 				break;
 
+			case OP_SET_ETS_VALUES:
+				multi_sexp_set_ets_values();
+				break;
+
 			// bad sexp in the packet
 			default: 
 				// probably just a version error where the host supports a SEXP but a client does not
@@ -24377,6 +24476,7 @@
 		case OP_CUTSCENES_GET_FOV:
 		case OP_NUM_VALID_ARGUMENTS:
 		case OP_STRING_GET_LENGTH:
+		case OP_GET_ETS_VALUE:
 			return OPR_POSITIVE;
 
 		case OP_COND:
@@ -24690,6 +24790,7 @@
 		case OP_NEBULA_CHANGE_PATTERN:
 		case OP_COPY_VARIABLE_FROM_INDEX:
 		case OP_COPY_VARIABLE_BETWEEN_INDEXES:
+		case OP_SET_ETS_VALUES:
 			return OPR_NULL;
 
 		case OP_AI_CHASE:
@@ -26213,6 +26314,20 @@
 		case OP_ENGINE_RECHARGE_PCT:
 			return OPF_SHIP;			
 
+		case OP_GET_ETS_VALUE:
+			if (argnum == 0) {
+				return OPF_STRING;
+			} else {
+				return OPF_SHIP;
+			}
+
+		case OP_SET_ETS_VALUES:
+			if (argnum < 3) {
+				return OPF_POSITIVE;
+			} else {
+				return OPF_SHIP;
+			}
+
 		case OP_SHIELD_QUAD_LOW:
 			if(argnum == 0){
 				return OPF_SHIP;
@@ -28453,6 +28568,8 @@
 		case OP_SECONDARY_FIRED_SINCE:
 		case OP_HAS_PRIMARY_WEAPON:
 		case OP_HAS_SECONDARY_WEAPON:
+		case OP_GET_ETS_VALUE:
+		case OP_SET_ETS_VALUES:
 			return STATUS_SUBCATEGORY_SHIELDS_ENGINES_AND_WEAPONS;
 			
 		case OP_CARGO_KNOWN_DELAY:
@@ -31024,6 +31141,20 @@
 		"\tReturns a percentage from 0 to 100\r\n"
 		"\t1: Ship name\r\n" },
 
+	{ OP_GET_ETS_VALUE, "get-ets-values\r\n"
+		"\tGets one ETS index for a ship\r\n"
+		"\t1: ETS index to get, Engine|Shield|Weapon\r\n"
+		"\t2: Ship name\r\n"},
+
+	{ OP_SET_ETS_VALUES, "set-ets-values\r\n"
+		"\tSets ETS indexes for a ship\r\n"
+		"\tUse values retrieved with get-ets-value\r\n"
+		"\tIf you use your own values, make sure the add up to 12\r\n"
+		"\t1: Engine percent\r\n"
+		"\t2: Shields percent\r\n"
+		"\t3: Weapons percent\r\n"
+		"\t4: Ship name\r\n"},
+
 	{ OP_CARGO_NO_DEPLETE, "cargo-no-deplete\r\n"
 		"\tCauses the named ship to have unlimited cargo.\r\n"
 		"\tNote:  only applies to BIG or HUGE ships\r\n"
Index: code/parse/lua.cpp
===================================================================
--- code/parse/lua.cpp	(revision 9961)
+++ code/parse/lua.cpp	(working copy)
@@ -15,6 +15,7 @@
 #include "hud/hudbrackets.h"
 #include "hud/hudescort.h"
 #include "hud/hudconfig.h"
+#include "hud/hudets.h"
 #include "hud/hudgauges.h"
 #include "hud/hudets.h"
 #include "iff_defs/iff_defs.h"
@@ -8512,6 +8513,57 @@
 		return ADE_RETURN_FALSE;
 }
 
+ADE_VIRTVAR(EtsEngineIndex, l_Ship, "number", "(not implemented)", "number", "Ships ETS Engine index value, 0 to MAX_ENERGY_INDEX")
+{
+	object_h *objh=NULL;
+	int ets_idx = 0;
+
+	if (!ade_get_args(L, "o|i", l_Ship.GetPtr(&objh), &ets_idx))
+		return ade_set_error(L, "i", 0);
+
+	if (!objh->IsValid())
+		return ade_set_error(L, "i", 0);
+
+	if(ADE_SETTING_VAR)
+		LuaError(L, "Attempted to set incomplete feature: ETS Engine Index (see EtsSetIndexes)");
+
+	return ade_set_args(L, "i", Ships[objh->objp->instance].engine_recharge_index);
+}
+
+ADE_VIRTVAR(EtsShieldIndex, l_Ship, "number", "(not implemented)", "number", "Ships ETS Shield index value, 0 to MAX_ENERGY_INDEX")
+{
+	object_h *objh=NULL;
+	int ets_idx = 0;
+
+	if (!ade_get_args(L, "o|i", l_Ship.GetPtr(&objh), &ets_idx))
+		return ade_set_error(L, "i", 0);
+
+	if (!objh->IsValid())
+		return ade_set_error(L, "i", 0);
+
+	if(ADE_SETTING_VAR)
+		LuaError(L, "Attempted to set incomplete feature: ETS Shield Index (see EtsSetIndexes)");
+
+	return ade_set_args(L, "i", Ships[objh->objp->instance].shield_recharge_index);
+}
+
+ADE_VIRTVAR(EtsWeaponIndex, l_Ship, "number", "(not implemented)", "number", "Ships ETS Weapon index value, 0 to MAX_ENERGY_INDEX")
+{
+	object_h *objh=NULL;
+	int ets_idx = 0;
+
+	if (!ade_get_args(L, "o|i", l_Ship.GetPtr(&objh), &ets_idx))
+		return ade_set_error(L, "i", 0);
+
+	if (!objh->IsValid())
+		return ade_set_error(L, "i", 0);
+
+	if(ADE_SETTING_VAR)
+		LuaError(L, "Attempted to set incomplete feature: ETS Weapon Index (see EtsSetIndexes)");
+
+	return ade_set_args(L, "i", Ships[objh->objp->instance].weapon_recharge_index);
+}
+
 ADE_FUNC(kill, l_Ship, "[object Killer]", "Kills the ship. Set \"Killer\" to the ship you are killing to self-destruct", "boolean", "True if successful, false or nil otherwise")
 {
 	object_h *victim,*killer=NULL;
@@ -9162,6 +9214,33 @@
 	}
 }
 
+ADE_FUNC(EtsSetIndexes, l_Ship, "number Engine Index, number Shield Index, number Weapon Index",
+		"Sets ships ETS systems to specified values",
+		"boolean",
+		"True if successful, false if target ships ETS was missing, or only has one system")
+{
+	object_h *objh=NULL;
+	int ets_idx[num_retail_ets_gauges] = {0};
+
+	if (!ade_get_args(L, "oiii", l_Ship.GetPtr(&objh), &ets_idx[ENGINES], &ets_idx[SHIELDS], &ets_idx[WEAPONS]))
+		return ADE_RETURN_FALSE;
+
+	if (!objh->IsValid())
+		return ADE_RETURN_FALSE;
+
+	sanity_check_ets_inputs(ets_idx);
+
+	int sindex = objh->objp->instance;
+	if (validate_ship_ets_indxes(sindex, ets_idx)) {
+		Ships[sindex].engine_recharge_index = ets_idx[ENGINES];
+		Ships[sindex].shield_recharge_index = ets_idx[SHIELDS];
+		Ships[sindex].weapon_recharge_index = ets_idx[WEAPONS];
+		return ADE_RETURN_TRUE;
+	} else {
+		return ADE_RETURN_FALSE;
+	}
+}
+
 //**********HANDLE: Weapon
 ade_obj<object_h> l_Weapon("weapon", "Weapon handle", &l_Object);
 
Index: code/parse/sexp.h
===================================================================
--- code/parse/sexp.h	(revision 9961)
+++ code/parse/sexp.h	(working copy)
@@ -715,6 +715,8 @@
 
 #define OP_COPY_VARIABLE_FROM_INDEX			(0x0020 | OP_CATEGORY_CHANGE2 | OP_NONCAMPAIGN_FLAG) // Goober5000
 #define OP_COPY_VARIABLE_BETWEEN_INDEXES	(0x0021 | OP_CATEGORY_CHANGE2 | OP_NONCAMPAIGN_FLAG) // Goober5000
+#define OP_GET_ETS_VALUE					(0x0022 | OP_CATEGORY_CHANGE2 | OP_NONCAMPAIGN_FLAG)	// niffiwan
+#define OP_SET_ETS_VALUES					(0x0023 | OP_CATEGORY_CHANGE2 | OP_NONCAMPAIGN_FLAG)	// niffiwan
 
 // defined for AI goals
 #define OP_AI_CHASE							(0x0000 | OP_CATEGORY_AI | OP_NONCAMPAIGN_FLAG)
