Add admin-controlled self epinephrine for unconscious players
This commit is contained in:
@@ -90,6 +90,11 @@ ActionManager : "{795184CF9AD764DB}Configs/System/chimeraInputCommon.conf" {
|
||||
}
|
||||
ActionContext ERR_ObserverContext : "{C538807EEE48BED8}Configs/System/ActionContext/ManualCameraContext.conf" {
|
||||
Actions {
|
||||
Action ERR_SelfEpinephrine {
|
||||
InputSource InputSourceValue "{A7B19F416E541020}" {
|
||||
Input "keyboard:KC_SPACE"
|
||||
}
|
||||
}
|
||||
Action ERR_ObserverPrevious {
|
||||
InputSource InputSourceValue "{A7B19F416E541001}" {
|
||||
Input "keyboard:KC_LEFT"
|
||||
|
||||
@@ -131,7 +131,7 @@ class ERR_AdminMenu : ChimeraMenuBase
|
||||
m_Controller = SCR_PlayerController.Cast(GetGame().GetPlayerController());
|
||||
m_Mode = ERR_GameMode.Cast(GetGame().GetGameMode());
|
||||
if (!m_Controller) { Close(); return; }
|
||||
array<string> names = {"PlaneOn", "PlaneOff", "Close", "OpenSettings", "OpenWorld", "OpenFactions", "OpenHQ", "OpenJournal", "PauseJournal", "JournalDetails", "LatestJournal", "KeepWeaponOn", "KeepWeaponOff", "IntelSummary", "RerollIntel", "OpenMods", "OpenIntel", "OpenStatus", "OpenTerrains", "OpenItems", "OpenTTS"};
|
||||
array<string> names = {"PlaneOn", "PlaneOff", "Close", "OpenSettings", "OpenWorld", "OpenFactions", "OpenHQ", "OpenJournal", "PauseJournal", "JournalDetails", "LatestJournal", "KeepWeaponOn", "KeepWeaponOff", "SelfEpiOn", "SelfEpiOff", "IntelSummary", "RerollIntel", "OpenMods", "OpenIntel", "OpenStatus", "OpenTerrains", "OpenItems", "OpenTTS"};
|
||||
names.Insert("RandomDay_startTimeMinutes");
|
||||
names.Insert("Default_viewDistanceMetres");
|
||||
names.Insert("OpenForces");
|
||||
@@ -257,9 +257,11 @@ class ERR_AdminMenu : ChimeraMenuBase
|
||||
if (page == 2) m_LogPoll = 1;
|
||||
}
|
||||
protected int m_KeepWeaponState = -1;
|
||||
protected int m_SelfEpiState = -1;
|
||||
protected int m_RestartEmptyState = -1;
|
||||
protected void UpdateActions()
|
||||
{
|
||||
ERR_SelectionStyle.Pair(GetRootWidget(), "SelfEpiOn", "SelfEpiOff", m_SelfEpiState);
|
||||
ERR_SelectionStyle.Pair(GetRootWidget(), "KeepWeaponOn", "KeepWeaponOff", m_KeepWeaponState);
|
||||
ERR_SelectionStyle.Pair(GetRootWidget(), "RestartEmptyOn", "RestartEmptyOff", m_RestartEmptyState);
|
||||
foreach (SCR_ButtonBaseComponent button : m_Buttons)
|
||||
@@ -278,6 +280,8 @@ class ERR_AdminMenu : ChimeraMenuBase
|
||||
else if (name == "LatestJournal") button.SetEnabled(m_Ready && m_HQ);
|
||||
else if (name == "RestartEmptyOn") button.SetEnabled(m_Ready && m_RestartEmptyState == 0);
|
||||
else if (name == "RestartEmptyOff") button.SetEnabled(m_Ready && m_RestartEmptyState == 1);
|
||||
else if (name == "SelfEpiOn") button.SetEnabled(m_Ready && m_SelfEpiState == 0);
|
||||
else if (name == "SelfEpiOff") button.SetEnabled(m_Ready && m_SelfEpiState == 1);
|
||||
else if (name == "KeepWeaponOn") button.SetEnabled(m_Ready && m_KeepWeaponState == 0);
|
||||
else if (name == "KeepWeaponOff") button.SetEnabled(m_Ready && m_KeepWeaponState == 1);
|
||||
}
|
||||
@@ -286,7 +290,7 @@ class ERR_AdminMenu : ChimeraMenuBase
|
||||
{
|
||||
if (!authorized) { Close(); return; }
|
||||
if (!values) return;
|
||||
m_HQ = false; m_Intel = false; m_AvailableFields.Clear(); m_KeepWeaponState = -1; m_RestartEmptyState = -1;
|
||||
m_HQ = false; m_Intel = false; m_AvailableFields.Clear(); m_KeepWeaponState = -1; m_SelfEpiState = -1; m_RestartEmptyState = -1;
|
||||
foreach (string entry : values)
|
||||
{
|
||||
if (entry == "supports=hq") { m_HQ = true; continue; }
|
||||
@@ -330,6 +334,11 @@ class ERR_AdminMenu : ChimeraMenuBase
|
||||
SetText("Saved_restartOnEmpty", "Saved: " + restartState);
|
||||
continue;
|
||||
}
|
||||
if (key == "selfEpinephrine")
|
||||
{
|
||||
m_SelfEpiState = entry.Substring(divider + 1, entry.Length() - divider - 1).ToInt();
|
||||
continue;
|
||||
}
|
||||
if (key == "keepWeaponOnUnconscious")
|
||||
{
|
||||
m_KeepWeaponState = entry.Substring(divider + 1, entry.Length() - divider - 1).ToInt();
|
||||
@@ -485,6 +494,13 @@ class ERR_AdminMenu : ChimeraMenuBase
|
||||
m_ClickDelay = 0.3; SetText("Feedback", "Saving restart after last disconnect...");
|
||||
m_Controller.ERR_AdminSubmit("mission restartempty set " + restartEnabled, true); return;
|
||||
}
|
||||
if (name == "SelfEpiOn" || name == "SelfEpiOff")
|
||||
{
|
||||
if (m_SelfEpiState < 0) return;
|
||||
m_ClickDelay = 0.3; SetText("Feedback", "Saving self epinephrine permission...");
|
||||
string selfEpi = "0"; if (name == "SelfEpiOn") selfEpi = "1";
|
||||
m_Controller.ERR_AdminSubmit("mission selfepi set " + selfEpi, true); return;
|
||||
}
|
||||
if (name == "KeepWeaponOn" || name == "KeepWeaponOff")
|
||||
{
|
||||
string retain = "0"; if (name == "KeepWeaponOn") retain = "1";
|
||||
|
||||
@@ -22,6 +22,7 @@ class ERR_CommandArguments
|
||||
if (section == "mission" && option == "roadblocks") return "roadblockCount";
|
||||
if (section == "mission" && option == "traffic") return "civilianTrafficCount";
|
||||
if (section == "mission" && option == "start") return "startMode";
|
||||
if (section == "mission" && option == "selfepi") return "selfEpinephrine";
|
||||
if (section == "mission" && option == "keepweapon") return "keepWeaponOnUnconscious";
|
||||
if (section == "mission" && option == "restartempty") return "restartOnEmpty";
|
||||
if (section == "mission" && option == "emptyhours") return "emptyRestartHours";
|
||||
@@ -184,6 +185,7 @@ class ERR_AdminService
|
||||
if (key == "restartOnEmpty") return "On (1) restarts after the last disconnect; Off (0) preserves the round for reconnects. Default On. Applies immediately; normal round endings still apply.";
|
||||
if (key == "emptyRestartHours") return "0 disables idle restarts; 1..168 real hours continuously empty. Default 5. Applies immediately, measured from mission start or the last disconnect. Connected players, including spectators, prevent idle restarts.";
|
||||
if (key == "civilianTrafficCount") return "Set 0..6 civilian cars (default 3). Zero disables new traffic. Unclaimed cars retire only far from all players and out of sight; player-entered cars are preserved.";
|
||||
if (key == "selfEpinephrine") return "0 disabled (default), 1 enabled. Unconscious players can press Space to use a carried epinephrine injector. Applies immediately; consumes the item and keeps its normal medical effect.";
|
||||
if (key == "keepWeaponOnUnconscious") return "Set 1 to retain the held firearm during unconsciousness and equip it after recovery; 0 keeps normal dropping. Applies to the next knockout.";
|
||||
if (key == "antiAirChancePercent" || key == "antiPersonnelMineChancePercent")
|
||||
return "Set 0..100 percent or Random. Applies to newly equipped enemy soldiers; existing loadouts are not rerolled or replenished.";
|
||||
@@ -224,6 +226,7 @@ class ERR_AdminService
|
||||
if (topic == "time" || topic == "mission")
|
||||
{
|
||||
replies.Insert("/esc mission radio show|set N|random | mission abandon");
|
||||
replies.Insert("/esc mission selfepi show|set 0|1; allow unconscious self-injection with Space. Disabled by default.");
|
||||
replies.Insert("/esc mission restartempty show|set on|off | mission emptyhours show|set 0..168; applies now, zero hours disables idle restarts.");
|
||||
replies.Insert("/esc mission doctrine show|set current|coordinated | mission aishadow show|set off|on; next round only.");
|
||||
replies.Insert("/esc mission start show|set prison|crash: save the next round start; current round unchanged.");
|
||||
|
||||
@@ -55,7 +55,7 @@ modded class ERR_GameMode
|
||||
string enabled;
|
||||
if (SCR_Global.IsEditMode() || !Replication.IsServer() || !System.GetCLIParam("errBaseSmoke", enabled) || enabled != "1") return;
|
||||
string nativeEndCase;
|
||||
array<string> independentFixtures = {"coordinated-ai", "optional-site-skip", "infantry-combat", "medical-combat", "hunter-faction", "unconscious-camera", "objective-map", "town-garrisons", "site-garrisons", "radio-interaction", "enemy-loadout", "aggression", "native-end-active", "native-end-preparing", "generated-sites", "roadblocks", "mortar-station", "opening-tools", "airfield-markers", "air-logistics", "air-assault", "air-recon"};
|
||||
array<string> independentFixtures = {"coordinated-ai", "optional-site-skip", "infantry-combat", "medical-combat", "hunter-faction", "unconscious-camera", "self-epinephrine", "objective-map", "town-garrisons", "site-garrisons", "radio-interaction", "enemy-loadout", "aggression", "native-end-active", "native-end-preparing", "generated-sites", "roadblocks", "mortar-station", "opening-tools", "airfield-markers", "air-logistics", "air-assault", "air-recon"};
|
||||
if (System.GetCLIParam("errSmokeOutcome", nativeEndCase) && independentFixtures.Contains(nativeEndCase)) return;
|
||||
ERR_NativeObjectiveProbe.BeginRound();
|
||||
ERR_NativeProgressionIdentityProbe.BeginRound();
|
||||
|
||||
@@ -127,11 +127,12 @@ class ERR_FollowSurvivorCameraComponent : SCR_BaseManualCameraComponent
|
||||
protected ref ERR_ObserverScopeDisplay m_ScopeDisplay = new ERR_ObserverScopeDisplay();
|
||||
protected ref ERR_ObserverPIPDisplay m_PIPDisplay = new ERR_ObserverPIPDisplay();
|
||||
protected bool m_InputContextActive;
|
||||
protected bool m_SpaceDown;
|
||||
bool InputContextActive() { return m_InputContextActive; }
|
||||
bool IsPlayerView() { return m_PlayerView; }
|
||||
// Process one edge per press; menus still consume the state so holding a key
|
||||
// while closing a menu cannot unexpectedly change the followed teammate.
|
||||
void Input(bool enabled, bool left, bool right, bool enter)
|
||||
void Input(bool enabled, bool left, bool right, bool enter, bool space = false)
|
||||
{
|
||||
// An inactive context reports zero even while a physical key is held. Let
|
||||
// the reactivated context settle, then require release before accepting keys.
|
||||
@@ -139,16 +140,20 @@ class ERR_FollowSurvivorCameraComponent : SCR_BaseManualCameraComponent
|
||||
else if (m_InputResumeFrames > 0)
|
||||
{
|
||||
enabled = false;
|
||||
if (m_InputResumeFrames > 1 || (!left && !right && !enter)) m_InputResumeFrames--;
|
||||
if (m_InputResumeFrames > 1 || (!left && !right && !enter && !space)) m_InputResumeFrames--;
|
||||
}
|
||||
SCR_PlayerController controller = SCR_PlayerController.Cast(GetGame().GetPlayerController());
|
||||
int cycle = ERR_ObserverRules.Cycle(enabled, left, right, m_LeftDown, m_RightDown);
|
||||
if (controller && cycle != 0) controller.ERR_CycleObserver(cycle);
|
||||
if (ERR_ObserverRules.Toggle(enabled, enter, m_EnterDown)) m_PlayerView = !m_PlayerView;
|
||||
if (controller && ERR_ObserverRules.Toggle(enabled, space, m_SpaceDown)) controller.ERR_UseSelfEpinephrine();
|
||||
m_SpaceDown = space;
|
||||
m_LeftDown = left; m_RightDown = right; m_EnterDown = enter;
|
||||
}
|
||||
override bool EOnCameraInit()
|
||||
{
|
||||
// Release a held jump key after knockout before accepting self-injection.
|
||||
m_InputResumeFrames = 2;
|
||||
m_OrbitFov = GetCameraEntity().GetVerticalFOV();
|
||||
m_OrbitNear = GetCameraEntity().GetNearPlane();
|
||||
return true;
|
||||
@@ -159,7 +164,7 @@ class ERR_FollowSurvivorCameraComponent : SCR_BaseManualCameraComponent
|
||||
SCR_PlayerController controller = SCR_PlayerController.Cast(GetGame().GetPlayerController());
|
||||
if (!controller || GetGame().GetCameraManager().CurrentCamera() != GetCameraEntity()) { m_PIPDisplay.Close(); m_ScopeDisplay.Close(); m_ViewHead.Restore(); return; }
|
||||
m_InputContextActive = GetInputManager().IsContextActive("ERR_ObserverContext");
|
||||
Input(param.isManualInputEnabled, GetInputManager().GetActionValue("ERR_ObserverPrevious") > 0.5, GetInputManager().GetActionValue("ERR_ObserverNext") > 0.5, GetInputManager().GetActionValue("ERR_ObserverView") > 0.5);
|
||||
Input(param.isManualInputEnabled, GetInputManager().GetActionValue("ERR_ObserverPrevious") > 0.5, GetInputManager().GetActionValue("ERR_ObserverNext") > 0.5, GetInputManager().GetActionValue("ERR_ObserverView") > 0.5, GetInputManager().GetActionValue("ERR_SelfEpinephrine") > 0.5);
|
||||
IEntity target = controller.ERR_ObservedEntity();
|
||||
float fov, nearPlane;
|
||||
if (m_PlayerView && controller.ERR_ReadPlayerView(target, param.timeSlice, param.transform, fov, nearPlane))
|
||||
@@ -283,6 +288,9 @@ modded class SCR_PlayerController
|
||||
protected IEntity m_ERRRecoveryBody;
|
||||
protected float m_ERRRecoveryDeadline = -1;
|
||||
|
||||
// Optional casualty action is implemented by the self-epinephrine extension.
|
||||
string ERR_SelfEpinephrineHint() { return ""; }
|
||||
void ERR_UseSelfEpinephrine() {}
|
||||
ERR_ObserverReason ERR_ObserverState() { return m_ERRObserverReason; }
|
||||
vector ERR_ObserverAnchor() { return m_ERRObserverAnchor; }
|
||||
IEntity ERR_ObservedEntity()
|
||||
@@ -458,6 +466,8 @@ modded class SCR_PlayerController
|
||||
// whose local entity is still streaming. Never imply no survivors from delay.
|
||||
bool survivorAvailable = m_ERRObservedId != RplId.Invalid();
|
||||
string recovery = mode.RecoveryStatus();
|
||||
string selfEpiHint = ERR_SelfEpinephrineHint();
|
||||
if (!selfEpiHint.IsEmpty()) recovery += "\n" + selfEpiHint;
|
||||
if (ERR_ObserverGuidance.Refresh(m_ERRObserverReady, m_ERRShownObserverReason, m_ERRObserverReason, m_ERRShownObserverAvailable, survivorAvailable) || m_ERRShownRecoveryStatus != recovery)
|
||||
{
|
||||
SCR_HintManagerComponent hints = SCR_HintManagerComponent.GetInstance();
|
||||
|
||||
@@ -64,7 +64,7 @@ class ERR_ScenarioComponent : ScriptComponent
|
||||
#ifdef WORKBENCH
|
||||
string historicalFixture;
|
||||
if (!m_TerrainIndependent && System.GetCLIParam("errSmokeOutcome", historicalFixture) && historicalFixture != "normal-escape-route" && historicalFixture != "crash-start" && historicalFixture != "mounted-ammo" && historicalFixture != "alarm-hearing" && historicalFixture != "optional-site-skip" && historicalFixture != "infantry-combat" && historicalFixture != "site-garrisons" && historicalFixture != "generated-sites" && historicalFixture != "roadblocks" && historicalFixture != "opening-tools" && historicalFixture != "airfield-markers" && historicalFixture != "air-recon" && historicalFixture != "air-logistics" && historicalFixture != "air-assault")
|
||||
m_Definition = "{ACA98B44E2173605}Configs/ERR/Tests/LegacySites.conf";
|
||||
if (historicalFixture != "self-epinephrine") m_Definition = "{ACA98B44E2173605}Configs/ERR/Tests/LegacySites.conf";
|
||||
#endif
|
||||
m_RuntimeDefinition = ERR_ScenarioDefinition.Cast(SCR_BaseContainerTools.CreateInstanceFromPrefab(m_Definition));
|
||||
if (!m_RuntimeDefinition || !m_RuntimeDefinition.Valid()) { m_Failed = true; return ERR_PrepareResult.FAILED; }
|
||||
@@ -72,7 +72,7 @@ class ERR_ScenarioComponent : ScriptComponent
|
||||
// Existing deterministic probes intentionally use authored coordinates.
|
||||
string fixture;
|
||||
if (!m_TerrainIndependent && System.GetCLIParam("errSmokeOutcome", fixture) && fixture != "normal-escape-route" && fixture != "crash-start" && fixture != "mounted-ammo" && fixture != "alarm-hearing" && fixture != "optional-site-skip" && fixture != "infantry-combat" && fixture != "site-garrisons" && fixture != "generated-sites" && fixture != "roadblocks" && fixture != "airfield-markers" && fixture != "air-recon" && fixture != "air-logistics" && fixture != "air-assault")
|
||||
foreach (ERR_SiteDefinition fixedSite : m_RuntimeDefinition.Sites) fixedSite.RandomClear = false;
|
||||
if (fixture != "self-epinephrine") foreach (ERR_SiteDefinition fixedSite : m_RuntimeDefinition.Sites) fixedSite.RandomClear = false;
|
||||
if (!ERR_ConfigurePatrolHQFixture(fixture)) { m_Failed = true; return ERR_PrepareResult.FAILED; }
|
||||
#endif
|
||||
if (m_Progressive && !m_RuntimeDefinition.ConfigurePopulation(m_Definition, mode.Settings().Get("fieldCampCount"), mode.Settings().Get("fieldHospitalCount")))
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
// Optional casualty assistance. The authoritative controller supplies the body;
|
||||
// clients cannot nominate another patient, an item, or an enabled setting.
|
||||
class ERR_SelfEpinephrineRules
|
||||
{
|
||||
static bool Allowed(bool active, bool enabled, bool currentBody, bool alive, bool unconscious)
|
||||
{
|
||||
return active && enabled && currentBody && alive && unconscious;
|
||||
}
|
||||
}
|
||||
modded class SCR_PlayerController
|
||||
{
|
||||
[RplProp()] protected bool m_ERRSelfEpinephrineEnabled;
|
||||
protected float m_ERRLastSelfEpinephrine = -10000;
|
||||
override void ERR_UpdateObserverServer(int direction = 0)
|
||||
{
|
||||
super.ERR_UpdateObserverServer(direction);
|
||||
if (!Replication.IsServer()) return;
|
||||
ERR_GameMode mode = ERR_GameMode.Get();
|
||||
bool enabled = mode && mode.Active() && mode.Settings().Get("selfEpinephrine") == 1;
|
||||
if (enabled == m_ERRSelfEpinephrineEnabled) return;
|
||||
m_ERRSelfEpinephrineEnabled = enabled; Replication.BumpMe();
|
||||
}
|
||||
override string ERR_SelfEpinephrineHint()
|
||||
{
|
||||
if (!m_ERRSelfEpinephrineEnabled || ERR_ObserverState() != ERR_ObserverReason.UNCONSCIOUS) return "";
|
||||
return "Press Space to use an epinephrine injector from your inventory.";
|
||||
}
|
||||
override void ERR_UseSelfEpinephrine()
|
||||
{
|
||||
if (this != GetGame().GetPlayerController() || !m_ERRSelfEpinephrineEnabled || ERR_ObserverState() != ERR_ObserverReason.UNCONSCIOUS) return;
|
||||
Rpc(ERR_RequestSelfEpinephrine);
|
||||
}
|
||||
[RplRpc(RplChannel.Reliable, RplRcver.Server)]
|
||||
protected void ERR_RequestSelfEpinephrine()
|
||||
{
|
||||
if (!Replication.IsServer()) return;
|
||||
float now = GetGame().GetWorld().GetWorldTime();
|
||||
if (now >= m_ERRLastSelfEpinephrine && now - m_ERRLastSelfEpinephrine < 1000) return;
|
||||
m_ERRLastSelfEpinephrine = now;
|
||||
string result = ERR_TrySelfEpinephrine();
|
||||
if (!result.IsEmpty()) Rpc(ERR_SelfEpinephrineResponse, result);
|
||||
}
|
||||
protected string ERR_TrySelfEpinephrine()
|
||||
{
|
||||
if (!Replication.IsServer()) return "";
|
||||
ERR_GameMode mode = ERR_GameMode.Get();
|
||||
IEntity actor = GetControlledEntity();
|
||||
CharacterControllerComponent controller;
|
||||
if (actor) controller = CharacterControllerComponent.Cast(actor.FindComponent(CharacterControllerComponent));
|
||||
ERR_PlayerLife life;
|
||||
if (mode && mode.Players()) life = mode.Players().Get(GetPlayerId());
|
||||
if (!mode || !ERR_SelfEpinephrineRules.Allowed(mode.Active(), mode.Settings().Get("selfEpinephrine") == 1,
|
||||
life && life.Actor == actor && mode.Players().CurrentActor(actor) == life, ERR_Actors.Alive(actor), controller && controller.IsUnconscious())) return "";
|
||||
InventoryStorageManagerComponent inventory = InventoryStorageManagerComponent.Cast(actor.FindComponent(InventoryStorageManagerComponent));
|
||||
array<IEntity> items = {};
|
||||
if (inventory) inventory.GetItems(items);
|
||||
if (items.Count() > 512) return "Unable to inspect your medical supplies.";
|
||||
// Prefer the standard injector; preserve each item's ordinary medical effect.
|
||||
for (int kind = 0; kind < 2; kind++)
|
||||
foreach (IEntity injector : items)
|
||||
{
|
||||
if (!injector || !inventory.Contains(injector)) continue;
|
||||
SCR_ConsumableItemComponent item = SCR_ConsumableItemComponent.Cast(injector.FindComponent(SCR_ConsumableItemComponent));
|
||||
if (!item) continue;
|
||||
SCR_ConsumableMorphine effect = SCR_ConsumableMorphine.Cast(item.GetConsumableEffect());
|
||||
if (!effect) continue;
|
||||
if (kind == 0 && !ERR_ConsumableEpinephrine.Cast(effect)) continue;
|
||||
if (kind == 1 && !ERR_ConsumableCombatEpinephrine.Cast(effect)) continue;
|
||||
SCR_EConsumableFailReason reason;
|
||||
if (!effect.CanApplyEffect(actor, actor, reason)) continue;
|
||||
// An unconscious player cannot perform the standing injection animation.
|
||||
// Use the native effect/consumption path on this exact carried item once.
|
||||
item.ApplyItemEffect(actor, actor, effect.GetAnimationParameters(injector, actor), injector, effect.GetDeleteOnUse());
|
||||
return "Epinephrine administered. Its normal medical effect is active.";
|
||||
}
|
||||
return "No usable epinephrine injector in your inventory, or treatment is already active.";
|
||||
}
|
||||
[RplRpc(RplChannel.Reliable, RplRcver.Owner)]
|
||||
protected void ERR_SelfEpinephrineResponse(string message)
|
||||
{
|
||||
if (this != GetGame().GetPlayerController()) return;
|
||||
SCR_HintManagerComponent hints = SCR_HintManagerComponent.GetInstance();
|
||||
if (hints) hints.ShowCustom(message, "Self epinephrine", 6);
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ class ERR_Settings
|
||||
|
||||
static array<string> Keys()
|
||||
{
|
||||
return {"aiDoctrine", "aiShadow", "motorbikeCount", "planeAltitudeMetres", "planeReplacementSeconds", "planeSectorSeconds", "planeReconEnabled", "fieldCampCount", "fieldHospitalCount", "startWeaponCount", "startMagazineCount", "objectGuardCount", "objectGuardEvery", "objectGuardHQMode", "hunterSpawnDistanceMetres", "roadblockCount", "civilianTrafficCount", "startMode", "keepWeaponOnUnconscious", "restartOnEmpty", "emptyRestartHours", "reportIntervalSeconds", "supportSquadLimit", "responseRadiusMetres", "aiIntelChancePercent", "intelRevealChancePercent", "radioDefenceSeconds", "startTimeMinutes", "timeMultiplier", "startDateCode", "weatherProfile", "viewDistanceMetres", "antiAirChancePercent", "antiPersonnelMineChancePercent", "automaticEquipmentChancePercent", "automaticConvoyEnabled", "convoyCount", "heliTimerMinutes"};
|
||||
return {"aiDoctrine", "aiShadow", "motorbikeCount", "planeAltitudeMetres", "planeReplacementSeconds", "planeSectorSeconds", "planeReconEnabled", "fieldCampCount", "fieldHospitalCount", "startWeaponCount", "startMagazineCount", "objectGuardCount", "objectGuardEvery", "objectGuardHQMode", "hunterSpawnDistanceMetres", "roadblockCount", "civilianTrafficCount", "startMode", "keepWeaponOnUnconscious", "selfEpinephrine", "restartOnEmpty", "emptyRestartHours", "reportIntervalSeconds", "supportSquadLimit", "responseRadiusMetres", "aiIntelChancePercent", "intelRevealChancePercent", "radioDefenceSeconds", "startTimeMinutes", "timeMultiplier", "startDateCode", "weatherProfile", "viewDistanceMetres", "antiAirChancePercent", "antiPersonnelMineChancePercent", "automaticEquipmentChancePercent", "automaticConvoyEnabled", "convoyCount", "heliTimerMinutes"};
|
||||
}
|
||||
|
||||
static bool Valid(string key, int value)
|
||||
@@ -31,7 +31,7 @@ class ERR_Settings
|
||||
if (key == "roadblockCount") return value >= 0 && value <= 30;
|
||||
if (key == "civilianTrafficCount") return value >= 0 && value <= 6;
|
||||
if (key == "startMode") return value == 0 || value == 1;
|
||||
if (key == "keepWeaponOnUnconscious" || key == "restartOnEmpty") return value == 0 || value == 1;
|
||||
if (key == "selfEpinephrine" || key == "keepWeaponOnUnconscious" || key == "restartOnEmpty") return value == 0 || value == 1;
|
||||
if (key == "emptyRestartHours") return value >= 0 && value <= 168;
|
||||
if (key == "heliTimerMinutes") return value >= 1 && value <= 1440;
|
||||
if (key == "convoyCount") return value >= 0 && value <= 20;
|
||||
@@ -132,7 +132,7 @@ class ERR_Settings
|
||||
if (key == "hunterSpawnDistanceMetres") return 1000;
|
||||
if (key == "roadblockCount") return 4;
|
||||
if (key == "civilianTrafficCount") return 3;
|
||||
if (key == "startMode") return 0;
|
||||
if (key == "startMode" || key == "selfEpinephrine") return 0;
|
||||
if (key == "keepWeaponOnUnconscious" || key == "restartOnEmpty") return 1;
|
||||
if (key == "emptyRestartHours") return 5;
|
||||
if (key == "heliTimerMinutes") return 15;
|
||||
@@ -169,6 +169,7 @@ class ERR_Settings
|
||||
m_Values.Set("civilianTrafficCount", AddedDefault("civilianTrafficCount"));
|
||||
m_Values.Set("startMode", AddedDefault("startMode"));
|
||||
m_Values.Set("keepWeaponOnUnconscious", AddedDefault("keepWeaponOnUnconscious"));
|
||||
m_Values.Set("selfEpinephrine", AddedDefault("selfEpinephrine"));
|
||||
m_Values.Set("restartOnEmpty", AddedDefault("restartOnEmpty"));
|
||||
m_Values.Set("emptyRestartHours", AddedDefault("emptyRestartHours"));
|
||||
m_Values.Set("reportIntervalSeconds", 10);
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
#ifdef WORKBENCH
|
||||
modded class SCR_PlayerController
|
||||
{
|
||||
string ERR_ProbeSelfEpiAttempt() { return ERR_TrySelfEpinephrine(); }
|
||||
}
|
||||
modded class ERR_AdminMenu
|
||||
{
|
||||
bool ERR_EpiProbeReady(int state) { return m_Ready && m_SelfEpiState == state; }
|
||||
bool ERR_EpiProbeEnable()
|
||||
{
|
||||
SCR_ButtonTextComponent button = SCR_ButtonTextComponent.GetButtonText("SelfEpiOn", GetRootWidget());
|
||||
if (!button || !button.IsEnabled()) return false;
|
||||
OnButton(button); return true;
|
||||
}
|
||||
}
|
||||
modded class ERR_GameMode
|
||||
{
|
||||
protected int m_ERRSelfTestStage, m_ERRSelfTestDeadline;
|
||||
protected float m_ERRSelfTestWait;
|
||||
protected IEntity m_ERRSelfTestBody, m_ERRSelfEpi, m_ERRSelfEpiSpare, m_ERRSelfCombatEpi;
|
||||
protected SCR_CharacterDamageManagerComponent m_ERRSelfTestDamage;
|
||||
protected override void EOnInit(IEntity owner)
|
||||
{
|
||||
super.EOnInit(owner);
|
||||
string outcome;
|
||||
if (!Replication.IsServer() || SCR_Global.IsEditMode() || !System.GetCLIParam("errSmokeOutcome", outcome) || outcome != "self-epinephrine") return;
|
||||
m_ERRSelfTestDeadline = System.GetTickCount() + 360000;
|
||||
GetGame().GetCallqueue().CallLater(ERR_SelfTestProbeTick, 500, true);
|
||||
OnStop().Insert(ERR_StopSelfEpiProbe);
|
||||
}
|
||||
protected void ERR_StopSelfEpiProbe() { GetGame().GetCallqueue().Remove(ERR_SelfTestProbeTick); }
|
||||
protected bool ERR_SelfTestCheck(bool ok, string detail)
|
||||
{
|
||||
if (ok) { Print("ERR SMOKE PASS: self epinephrine " + detail); return true; }
|
||||
Print("ERR SMOKE FAIL: self epinephrine " + detail, LogLevel.ERROR); ERR_StopSelfEpiProbe(); return false;
|
||||
}
|
||||
protected void ERR_SelfTestKnockout()
|
||||
{
|
||||
m_ERRSelfTestDamage.ForceUnconsciousness();
|
||||
array<ref SCR_PersistentDamageEffect> effects = m_ERRSelfTestDamage.GetAllPersistentEffectsOfType(SCR_PassiveHitZoneRegenDamageEffect);
|
||||
foreach (SCR_PersistentDamageEffect effect : effects) m_ERRSelfTestDamage.TerminateDamageEffect(effect);
|
||||
m_ERRSelfTestDamage.SetGroupIsBeingHealed(ECharacterHitZoneGroup.RIGHTARM, true);
|
||||
}
|
||||
protected void ERR_SelfTestProbeTick()
|
||||
{
|
||||
if (System.GetTickCount() > m_ERRSelfTestDeadline || !PreparationError().IsEmpty()) { ERR_SelfTestCheck(false, "deadline or preparation error"); return; }
|
||||
if (!Active()) return;
|
||||
SCR_PlayerController pc = SCR_PlayerController.Cast(GetGame().GetPlayerController());
|
||||
if (!pc || !pc.GetControlledEntity() || !Players().CurrentActor(pc.GetControlledEntity())) return;
|
||||
float now = GetWorld().GetWorldTime();
|
||||
if (now < m_ERRSelfTestWait) return;
|
||||
if (m_ERRSelfTestStage == 0)
|
||||
{
|
||||
m_ERRSelfTestBody = pc.GetControlledEntity();
|
||||
m_ERRSelfTestDamage = SCR_CharacterDamageManagerComponent.Cast(m_ERRSelfTestBody.FindComponent(SCR_CharacterDamageManagerComponent));
|
||||
if (!ERR_SelfTestCheck(m_ERRSelfTestDamage && m_ERRSelfTestDamage.ERR_IsRevivablePlayer(), "admitted player has treatable body")) return;
|
||||
ERR_SelfTestKnockout(); m_ERRSelfTestStage = 5; m_ERRSelfTestWait = now + 2000; return;
|
||||
}
|
||||
InventoryStorageManagerComponent playerInventory = InventoryStorageManagerComponent.Cast(m_ERRSelfTestBody.FindComponent(InventoryStorageManagerComponent));
|
||||
if (m_ERRSelfTestStage == 5)
|
||||
{
|
||||
if (!ERR_SelfTestCheck(pc.ERR_ProbeCameraActive(), "knockout opens observer camera")) return;
|
||||
if (!ERR_SelfTestCheck(Settings().Get("selfEpinephrine") == 0 && pc.ERR_SelfEpinephrineHint().IsEmpty(), "self epinephrine defaults off without a Space hint")) return;
|
||||
m_ERRSelfEpi = ERR_Actors.Spawn(ERR_EnemyMedicalCatalog.EPI, m_ERRSelfTestBody.GetOrigin());
|
||||
m_ERRSelfEpiSpare = ERR_Actors.Spawn(ERR_EnemyMedicalCatalog.EPI, m_ERRSelfTestBody.GetOrigin());
|
||||
if (!ERR_SelfTestCheck(m_ERRSelfEpi && m_ERRSelfEpiSpare && playerInventory.TryInsertItem(m_ERRSelfEpi) && playerInventory.TryInsertItem(m_ERRSelfEpiSpare), "two finite standard injectors carried by casualty")) return;
|
||||
if (!ERR_SelfTestCheck(pc.ERR_ProbeSelfEpiAttempt().IsEmpty() && m_ERRSelfEpi && m_ERRSelfEpiSpare, "authority refuses direct request while admin option disabled")) return;
|
||||
ERR_AdminMenu.Open();
|
||||
m_ERRSelfTestStage = 51; m_ERRSelfTestWait = now + 1500; return;
|
||||
}
|
||||
if (m_ERRSelfTestStage == 51)
|
||||
{
|
||||
ERR_AdminMenu menu = ERR_AdminMenu.GetOpenMenu();
|
||||
if (!menu || !menu.ERR_EpiProbeReady(0)) return;
|
||||
System.MakeScreenshot("$logs:err-self-epi-menu-disabled");
|
||||
if (!ERR_SelfTestCheck(menu.ERR_EpiProbeEnable(), "real admin menu button submits setting request")) return;
|
||||
m_ERRSelfTestStage = 52; m_ERRSelfTestWait = now + 1500; return;
|
||||
}
|
||||
if (m_ERRSelfTestStage == 52)
|
||||
{
|
||||
ERR_AdminMenu menu = ERR_AdminMenu.GetOpenMenu();
|
||||
if (!menu || !menu.ERR_EpiProbeReady(1)) return;
|
||||
if (!ERR_SelfTestCheck(Settings().Get("selfEpinephrine") == 1, "admin menu owner RPC saved enabled setting")) return;
|
||||
System.MakeScreenshot("$logs:err-self-epi-menu-enabled"); menu.Close();
|
||||
m_ERRSelfTestStage = 6; m_ERRSelfTestWait = now + 1500; return;
|
||||
}
|
||||
if (m_ERRSelfTestStage == 6)
|
||||
{
|
||||
if (!ERR_SelfTestCheck(!pc.ERR_SelfEpinephrineHint().IsEmpty() && GetGame().GetInputManager().IsActionActive("ERR_SelfEpinephrine"), "enabled hint and Space action active on existing casualty")) return;
|
||||
ERR_FollowSurvivorCameraComponent follow = pc.ERR_ProbeFollowComponent();
|
||||
follow.Input(false, false, false, false, true);
|
||||
follow.Input(true, false, false, false, true);
|
||||
follow.Input(true, false, false, false, true);
|
||||
if (!ERR_SelfTestCheck(m_ERRSelfEpi && m_ERRSelfEpiSpare, "disabled/menu-held Space cannot inject on resuming input")) return;
|
||||
follow.Input(true, false, false, false, false);
|
||||
follow.Input(true, false, false, false, true);
|
||||
follow.Input(true, false, false, false, true);
|
||||
m_ERRSelfTestStage = 7; m_ERRSelfTestWait = now + 1500; return;
|
||||
}
|
||||
if (m_ERRSelfTestStage == 7)
|
||||
{
|
||||
if (!ERR_SelfTestCheck((m_ERRSelfEpi != null) != (m_ERRSelfEpiSpare != null), "Space owner request consumes exactly one native standard injector")) return;
|
||||
if (!ERR_SelfTestCheck(!m_ERRSelfTestDamage.GetAllPersistentEffectsOfType(ERR_EpinephrineEffect).IsEmpty(), "actual standard epinephrine healing effect active on own casualty")) return;
|
||||
pc.ERR_ProbeSelfEpiAttempt();
|
||||
if (!ERR_SelfTestCheck((m_ERRSelfEpi != null) != (m_ERRSelfEpiSpare != null), "repeated request cannot consume spare while treatment already active")) return;
|
||||
m_ERRSelfTestDamage.SetGroupIsBeingHealed(ECharacterHitZoneGroup.RIGHTARM, false);
|
||||
m_ERRSelfTestStage = 8; m_ERRSelfTestWait = now + 30000; return;
|
||||
}
|
||||
if (m_ERRSelfTestStage == 8)
|
||||
{
|
||||
PrintFormat("ERR SELF EPI RECOVERY: blood=%1 resilience=%2 canFight=%3 observer=%4", m_ERRSelfTestDamage.GetBloodHitZone().GetHealthScaled(), m_ERRSelfTestDamage.GetResilienceHitZone().GetHealthScaled(), ERR_Actors.CanFight(m_ERRSelfTestBody), pc.ERR_HasObserverCamera());
|
||||
if (!ERR_SelfTestCheck(ERR_Actors.CanFight(m_ERRSelfTestBody) && !pc.ERR_HasObserverCamera(), "native self injection wakes original body and restores its camera")) return;
|
||||
if (!ERR_SelfTestCheck(pc.ERR_ProbeSelfEpiAttempt().IsEmpty(), "conscious player cannot use unconscious shortcut")) return;
|
||||
if (m_ERRSelfEpi) ERR_Actors.DeleteSpawned(m_ERRSelfEpi);
|
||||
if (m_ERRSelfEpiSpare) ERR_Actors.DeleteSpawned(m_ERRSelfEpiSpare);
|
||||
m_ERRSelfTestDamage.FullHeal(); ERR_SelfTestKnockout();
|
||||
m_ERRSelfCombatEpi = ERR_Actors.Spawn(ERR_EnemyMedicalCatalog.COMBAT_EPI, m_ERRSelfTestBody.GetOrigin());
|
||||
if (!ERR_SelfTestCheck(m_ERRSelfCombatEpi && playerInventory.TryInsertItem(m_ERRSelfCombatEpi), "one carried combat injector supplied for second self-treatment")) return;
|
||||
m_ERRSelfTestStage = 9; m_ERRSelfTestWait = now + 1500; return;
|
||||
}
|
||||
if (m_ERRSelfTestStage == 9)
|
||||
{
|
||||
if (!ERR_SelfTestCheck(Settings().Set("selfEpinephrine", 0) && pc.ERR_ProbeSelfEpiAttempt().IsEmpty() && m_ERRSelfCombatEpi, "live admin disable overrides stale client permission")) return;
|
||||
if (!ERR_SelfTestCheck(Settings().Set("selfEpinephrine", 1), "self injection reenabled")) return;
|
||||
string combatResult = pc.ERR_ProbeSelfEpiAttempt();
|
||||
PrintFormat("ERR SELF EPI COMBAT REQUEST: result=%1 item=%2 canFight=%3 resilience=%4", combatResult, m_ERRSelfCombatEpi, ERR_Actors.CanFight(m_ERRSelfTestBody), m_ERRSelfTestDamage.GetResilienceHitZone().GetHealthScaled());
|
||||
// Native unconscious/get-up transitions can outlast the instant medical effect.
|
||||
m_ERRSelfTestStage = 10; m_ERRSelfTestWait = now + 15000; return;
|
||||
}
|
||||
PrintFormat("ERR SELF EPI COMBAT RESULT: item=%1 blood=%2 resilience=%3 canFight=%4", m_ERRSelfCombatEpi, m_ERRSelfTestDamage.GetBloodHitZone().GetHealthScaled(), m_ERRSelfTestDamage.GetResilienceHitZone().GetHealthScaled(), ERR_Actors.CanFight(m_ERRSelfTestBody));
|
||||
if (!ERR_SelfTestCheck(!m_ERRSelfCombatEpi && ERR_Actors.CanFight(m_ERRSelfTestBody), "combat self injection consumed and restored consciousness through native effect")) return;
|
||||
Settings().Set("selfEpinephrine", 0);
|
||||
m_ERRSelfTestDamage.SetGroupIsBeingHealed(ECharacterHitZoneGroup.RIGHTARM, false);
|
||||
m_ERRSelfTestDamage.FullHeal();
|
||||
ERR_StopSelfEpiProbe(); Transition(ERR_Phase.DEFEAT);
|
||||
Print("ERR SMOKE COMPLETE: self epinephrine menu, owner RPC, Space handler, finite native consumption and recovery; scripted local fixture, not physical key or multiplayer proof");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -46,7 +46,9 @@ TextWidgetClass { Name "KeepWeaponLabel" Slot FrameWidgetSlot { Anchor 0 0 0 0 P
|
||||
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "KeepWeaponOn" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 268 OffsetTop 268 SizeX 140 OffsetRight -168 SizeY 32 OffsetBottom -300 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Enabled" } } }
|
||||
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "KeepWeaponOff" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 180 OffsetLeft 180 PositionY 268 OffsetTop 268 SizeX 152 OffsetRight -332 SizeY 32 OffsetBottom -300 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Disabled" } } }
|
||||
TextWidgetClass { Name "Saved_keepWeaponOnUnconscious" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 304 OffsetTop 304 SizeX 456 OffsetRight -484 SizeY 28 OffsetBottom -332 } Color 0.9 0.92 0.94 1 Text "Waiting for server" "Font Size" 14 "Min Font Size" 14 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||
TextWidgetClass { Name "MissionHelp" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 332 OffsetTop 332 SizeX 456 OffsetRight -484 SizeY 28 OffsetBottom -360 } Color 0.9 0.92 0.94 1 Text "Applies next knockout; existing casualties keep their rule." "Font Size" 14 "Min Font Size" 14 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||
TextWidgetClass { Name "SelfEpiLabel" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 28 OffsetLeft 28 PositionY 332 OffsetTop 332 SizeX 240 OffsetRight -268 SizeY 28 OffsetBottom -360 } Color 0.9 0.92 0.94 1 Text "Self epinephrine (Space)" "Font Size" 16 "Min Font Size" 16 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "SelfEpiOn" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 280 OffsetLeft 280 PositionY 332 OffsetTop 332 SizeX 90 OffsetRight -370 SizeY 28 OffsetBottom -360 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Enabled" } } }
|
||||
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "SelfEpiOff" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 382 OffsetLeft 382 PositionY 332 OffsetTop 332 SizeX 90 OffsetRight -472 SizeY 28 OffsetBottom -360 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Disabled" } } }
|
||||
TextWidgetClass { Name "Label_civilianTrafficCount" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 508 OffsetLeft 508 PositionY 244 OffsetTop 244 SizeX 456 OffsetRight -964 SizeY 22 OffsetBottom -266 } Color 0.9 0.92 0.94 1 Text "Civilian traffic (0-6 cars; 0 off)" "Font Size" 18 "Min Font Size" 18 Wrap 1 FontProperties FontProperties { Font "{3E7733BAC8C831F6}UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt" } }
|
||||
ButtonWidgetClass : "{DA43C77A0D1E59F8}UI/layouts/ERR/AdminValue.layout" { Name "civilianTrafficCount" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 508 OffsetLeft 508 PositionY 268 OffsetTop 268 SizeX 180 OffsetRight -688 SizeY 32 OffsetBottom -300 } components { SCR_EditBoxComponent "{547290FFBD5B33E9}" { m_sLabel "" } } }
|
||||
ButtonWidgetClass : "{75C912A1C89BE6C2}UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout" { Name "Apply_civilianTrafficCount" Slot FrameWidgetSlot { Anchor 0 0 0 0 PositionX 700 OffsetLeft 700 PositionY 268 OffsetTop 268 SizeX 100 OffsetRight -800 SizeY 32 OffsetBottom -300 } components { SCR_ButtonTextComponent "{5548F0143AE99095}" { m_sText "Apply" } } }
|
||||
|
||||
Reference in New Issue
Block a user