// Generates a random quip and publishes it public static void PlayQuip(AbstractActor source, List <string> quips, float showDuration = 3) { CastDef castDef = Coordinator.CreateCast(source); DialogueContent content = BuildContent(castDef, quips); source.Combat.MessageCenter.PublishMessage(new CustomDialogMessage(source, content, showDuration)); }
// The message type to send dialog through public CustomDialogMessage(AbstractActor dialogueSource, DialogueContent dialogueContent, float showDuration = 0f) : base() { this.dialogueSource = dialogueSource; this.dialogueSourceGUID = dialogueSource.GUID; this.dialogueContent = dialogueContent; this.showDuration = showDuration != 0f ? showDuration : dialogueContent.GetDialogueTime(); }
private void ParseNewDialogue(int id) { _DBC_Dialogue dialogue = dialogues.Search_Index_EQU(id); if (dialogue == null) { return; } currIndex = 0; currContent = new List <DialogueContent>(); string[] temps = dialogue.diglogueGroup.Split(']'); foreach (string item in temps) { if (item.Length <= 0) { continue; } string temp = item; temp = temp.Replace(",[", ""); temp = temp.Replace("[", ""); DialogueContent content = new DialogueContent(); int first = temp.IndexOf('/'); content.iconName = temp.Substring(0, first); int second = temp.IndexOf('/', first + 1); content.speakerName = temp.Substring(first + 1, second - first - 1); content.speakContent = temp.Substring(second + 1, temp.Length - second - 1); currContent.Add(content); } return; }
private void HandleDialogueInput() { if (currentDialogue == null || currentDialogue.selectionEvent == null) { return; } int index = currentDialogue.index; if (Input.GetKeyDown(KeyCode.S)) { currentDialogue.index = Math.Min(index + 1, currentDialogue.values.Count - 1); RenderDialogue(currentDialogue); } else if (Input.GetKeyDown(KeyCode.W)) { currentDialogue.index = Math.Max(0, index - 1); RenderDialogue(currentDialogue); } else if (Input.GetKeyDown(KeyCode.E)) { SelectionEvent ev = currentDialogue.selectionEvent; currentDialogue = null; ev.Invoke(index); } }
// Clone of DialogueContent::ApplyCastDef private static void ApplyCastDef(DialogueContent dialogueContent) { Contract contract = SharedState.Combat.ActiveContract; if (dialogueContent.selectedCastDefId == CastDef.castDef_TeamLeader_Employer) { TeamOverride teamOverride = contract.GameContext.GetObject(GameContextObjectTagEnum.TeamEmployer) as TeamOverride; dialogueContent.selectedCastDefId = teamOverride.teamLeaderCastDefId; } else if (dialogueContent.selectedCastDefId == CastDef.castDef_TeamLeader_EmployersAlly) { TeamOverride teamOverride2 = contract.GameContext.GetObject(GameContextObjectTagEnum.TeamEmployersAlly) as TeamOverride; dialogueContent.selectedCastDefId = teamOverride2.teamLeaderCastDefId; } else if (dialogueContent.selectedCastDefId == CastDef.castDef_TeamLeader_Target) { TeamOverride teamOverride3 = contract.GameContext.GetObject(GameContextObjectTagEnum.TeamTarget) as TeamOverride; dialogueContent.selectedCastDefId = teamOverride3.teamLeaderCastDefId; } else if (dialogueContent.selectedCastDefId == CastDef.castDef_TeamLeader_TargetsAlly) { TeamOverride teamOverride4 = contract.GameContext.GetObject(GameContextObjectTagEnum.TeamTargetsAlly) as TeamOverride; dialogueContent.selectedCastDefId = teamOverride4.teamLeaderCastDefId; } else if (dialogueContent.selectedCastDefId == CastDef.castDef_TeamLeader_Neutral) { TeamOverride teamOverride5 = contract.GameContext.GetObject(GameContextObjectTagEnum.TeamNeutralToAll) as TeamOverride; dialogueContent.selectedCastDefId = teamOverride5.teamLeaderCastDefId; } else if (dialogueContent.selectedCastDefId == CastDef.castDef_TeamLeader_Hostile) { TeamOverride teamOverride6 = contract.GameContext.GetObject(GameContextObjectTagEnum.TeamHostileToAll) as TeamOverride; dialogueContent.selectedCastDefId = teamOverride6.teamLeaderCastDefId; } }
public static DialogueContent BuildDialogueContent(CastDef castDef, string dialogue, Color dialogueColor) { if (castDef == null || String.IsNullOrEmpty(castDef.id) || castDef.defaultEmotePortrait == null || String.IsNullOrEmpty(castDef.defaultEmotePortrait.portraitAssetPath)) { Mod.Log.Warn?.Write("Was passed a castDef with an empty ID - we can't handle this!"); return(null); } Mod.Log.Info?.Write($"Creating dialogueContent for castDef: {castDef.id}"); DialogueContent content = new DialogueContent(dialogue, dialogueColor, castDef.id, null, null, DialogCameraDistance.Medium, DialogCameraHeight.Default, 0); // ContractInitialize normally sets the castDef on the content... no need, since we have the actual ref Traverse castDefT = Traverse.Create(content).Field("castDef"); castDefT.SetValue(castDef); // Initialize the active contract's team settings ApplyCastDef(content); // Load the default emote portrait Traverse dialogueSpriteCacheT = Traverse.Create(content).Field("dialogueSpriteCache"); Dictionary <string, Sprite> dialogueSpriteCache = dialogueSpriteCacheT.GetValue <Dictionary <string, Sprite> >(); Mod.Log.Debug?.Write($"Populating dialogueContent with sprite from path: {castDef.defaultEmotePortrait.portraitAssetPath}"); dialogueSpriteCache[castDef.defaultEmotePortrait.portraitAssetPath] = ModState.Portraits[castDef.defaultEmotePortrait.portraitAssetPath]; return(content); }
public static void PlayQuip(CombatGameState combat, string sourceGUID, Team team, string employerFactionName, List <string> quips, float showDuration = 3) { CastDef castDef = Coordinator.CreateCast(combat, sourceGUID, team, employerFactionName); DialogueContent content = BuildContent(castDef, quips); combat.MessageCenter.PublishMessage(new CustomDialogMessage(sourceGUID, content, showDuration)); }
/// <summary> /// Given a dialogue object, switch to the dialogue menu and start the dialogue sequence. /// </summary> /// <param name="dialogue">The dialogue object containing the speaker name and sentences.</param> public static void ShowDialogue(DialogueContent dialogue, Action onDialogueComplete = null) { GoInto("MenuDialogue"); MenuDialogueBehaviour menuDialogueBehaviour = GameObject.Find("Canvas").transform.Find("MenuDialogue") .GetComponent <MenuDialogueBehaviour>(); menuDialogueBehaviour.StartDialogue(dialogue, onDialogueComplete); }
public DialogueSystem(TextAsset text, Text textUI, Image faceImage, Image bgImage, Image toNextImage = null) { dialogue = new DialogueContent(text); dialogueText = textUI; this.faceImage = faceImage; this.bgImage = bgImage; toNextIcon = toNextImage; InitProperties(); }
// Generates a random quip and publishes it public static void PublishQuip(AbstractActor source, List <string> quips) { string quip = quips[Mod.Random.Next(0, quips.Count)]; string localizedQuip = new Localize.Text(quip).ToString(); CastDef castDef = Coordinator.CreateCast(source); DialogueContent content = Coordinator.BuildDialogueContent(castDef, localizedQuip, Color.white); Mod.Log.Info?.Write($"Publishing quip: {localizedQuip} with portrait: {castDef.defaultEmotePortrait.portraitAssetPath}"); source.Combat.MessageCenter.PublishMessage(new CustomDialogMessage(source, content, 3)); }
/// <summary> /// Play dialogue. /// </summary> /// <param name="newContent">DialogueContent - new content scritable object.Null by default.</param> public void PlayDialogue(DialogueContent newContent = null) { if (!playing && dialogueRoutine == null) { if (newContent != null) { SetUpContent(newContent); } dialogueRoutine = StartCoroutine(PlayDialogueRoutine()); } }
protected override void OnAppear(int sequence, int openOrder, WindowContext context) { base.OnAppear(sequence, openOrder, context); Content = context as DialogueContent; // LeftIcon.sprite = Content.LeftIcon; // RightIcon.sprite = Content.RightIcon; // Content.LeftModel.transform.SetParent(LeftModel); // Content.RightModel.transform.SetParent(RightModel); LoadDialogueData(Content.FileName); this.NodeData = GetDialogue(Content.StartNode); Coroutine = SingletonMono <GameFrameWork> .GetInstance().StartCoroutine(ShowContentText()); }
// Generates a random quip and publishes it public static void PublishQuip(AbstractActor source, List <string> quips) { string quip = quips[Mod.Random.Next(0, quips.Count)]; string localizedQuip = new Localize.Text(quip).ToString(); CastDef castDef = Coordinator.CreateCast(source); DialogueContent content = new DialogueContent( localizedQuip, Color.white, castDef.id, null, null, DialogCameraDistance.Medium, DialogCameraHeight.Default, 0 ); content.ContractInitialize(source.Combat); source.Combat.MessageCenter.PublishMessage(new CustomDialogMessage(source, content, 3)); }
/// <summary> /// Given a dialogue object, load the sentences and display the first sentence. /// </summary> /// <param name="dialogue">The dialogue object containing the speaker name and the sentences.</param> public void StartDialogue(DialogueContent dialogue, Action onDialogueComplete = null) { _speakerText.text = dialogue.Speaker; _sentences.Clear(); _onDialogueComplete = onDialogueComplete; foreach (string sentence in dialogue.Sentences) { _sentences.Enqueue(sentence); } DisplayNextSentence(); }
public DialogueSystem(TextAsset text, GameObject Dialogue) { dialogue = new DialogueContent(text); GameObject dialogueObject = Dialogue; dialogueText = dialogueObject.transform.Find("Text").gameObject.GetComponentInChildren <Text>(); faceImage = dialogueObject.transform.Find("Face").gameObject.GetComponentInChildren <Image>(); toNextIcon = dialogueObject.transform.Find("ToNextIcon").gameObject.GetComponentInChildren <Image>(); bgImage = dialogueObject.transform.GetComponent <Image>(); InitProperties(); }
public static ConversationContent CreateConversationContent(string presetDialogue, string cameraTargetGuid, CastDef cast = null) { CastDef castDef = (cast == null) ? RuntimeCastFactory.CreateCast() : cast; if (MissionControl.Instance.IsSkirmish()) { presetDialogue = Regex.Replace(presetDialogue, "{COMMANDER\\..+}", "Commander"); } DialogueContent dialogueContent1 = new DialogueContent( presetDialogue, Color.white, castDef.id, "", cameraTargetGuid, BattleTech.DialogCameraDistance.Medium, BattleTech.DialogCameraHeight.Default, -1 ); ConversationContent conversation = new ConversationContent("Conversation MC Test 1", new DialogueContent[] { dialogueContent1 }); return(conversation); }
/// <summary> /// Set up new dialogue content. /// </summary> /// <param name="newContent">DialogueContent - New dialogue content</param> public void SetUpContent(DialogueContent newContent) { this.content = newContent; }
private SelectionEvent SetDialogueContent(string text, List <string> selectionValues) { currentDialogue = new DialogueContent(text, selectionValues); RenderDialogue(currentDialogue); return(currentDialogue.selectionEvent); }
public void AddDialogue(DialogueContent dialogueContent) { AddDialogue(dialogueContent.title, dialogueContent.content); }
// Render the given DialogueContent instance onto the canvas. // Beware: This method does not care about the currently active // dialogue _at all_. It could lead to some weird bugs // if you call it outside of SetDialogueContent. private void RenderDialogue(DialogueContent content) { dialogueText.text = content.text; // If the caller did not specify any selection options we don't need // to do anything. In fact, we probably even want to hide the // scroll view. bool hasSelection = content.values != null && content.values.Count > 0; Text[] textChildArray = scrollContent.GetComponentsInChildren <Text>(); if (hasSelection) { List <Text> children = new List <Text>(textChildArray); // These two if branches make sure that we have the exact amount // of text entries needed to accomodate all selection options if (children.Count < content.values.Count) { int missing = content.values.Count - children.Count; for (int i = 0; i < missing; i++) { GameObject inst = Instantiate(selectionItemPrefab); inst.transform.SetParent(scrollContent.transform, false); children.Add(inst.GetComponent <Text>()); } } else if (children.Count > content.values.Count) { int tooMany = children.Count - content.values.Count; for (int i = 0; i < tooMany; i++) { // We need to remove from top to bottom, otherwise // the entries shift and we remove the wrong ones int index = children.Count - 1 - i; Destroy(children[index].gameObject); children.Remove(children[index]); } } int itemsPerPage = 2; int activePage = content.index / itemsPerPage; // Set text and color for all text entries for (int i = 0; i < children.Count; i++) { Text child = children[i]; int page = i / itemsPerPage; if (page == activePage) { child.enabled = true; child.text = content.values[i]; child.color = content.index == i ? highlightedColor : Color.black; } else { child.enabled = false; } if (content.index == i) { RectTransform scrollTransform = (RectTransform)scrollContent.transform; Vector2 scrollPosition = scrollTransform.anchoredPosition; // We make the assumption that all menu items have the same height RectTransform childTransform = (RectTransform)child.transform; scrollPosition.y = childTransform.rect.height * activePage * itemsPerPage; scrollTransform.anchoredPosition = scrollPosition; } } } else { foreach (Text child in textChildArray) { Destroy(child.gameObject); } } }
public CustomDialogMessage(string dialogueSourceGUID, DialogueContent dialogueContent, float showDuration = 0f) : base() { this.dialogueSourceGUID = dialogueSourceGUID; this.dialogueContent = dialogueContent; this.showDuration = showDuration != 0f ? showDuration : dialogueContent.GetDialogueTime(); }
public static void ProcessBatchedTurnDamage(AbstractActor actor) { int heatdamage = 0; if (ShouldSkipProcessing(actor)) { return; } AbstractActor attacker = TurnDamageTracker.attackActor(); LogReport($"\n{new string('═', 46)}"); LogReport($"Damage to {actor.DisplayName}/{actor.Nickname}/{actor.GUID}"); LogReport($"Damage by {attacker.DisplayName}/{attacker.Nickname}/{attacker.GUID}"); // get the attacker in case they have mech quirks AbstractActor defender = null; switch (actor) { case Vehicle _: defender = (Vehicle)actor; break; case Mech _: defender = (Mech)actor; break; } // a building or turret? if (defender == null) { LogDebug("Not a mech or vehicle"); return; } if (defender.IsDead || defender.IsFlaggedForDeath) { LogDebug("He's dead Jim....."); return; } var index = GetActorIndex(defender); if (modSettings.OneChangePerTurn && TrackedActors[index].PanicWorsenedRecently) { LogDebug($"OneChangePerTurn {defender.Nickname} - abort"); return; } float damageIncludingHeatDamage = 0; if (!modSettings.AlwaysPanic && !ShouldPanic(defender, attacker, out heatdamage, out damageIncludingHeatDamage)) { return; } // automatically eject a klutzy pilot on knockdown with an additional roll failing on 13 if (defender.IsFlaggedForKnockdown) { var defendingMech = (Mech)defender; if (defendingMech.pilot.pilotDef.PilotTags.Contains("pilot_klutz")) { if (Random.Range(1, 100) == 13) { defender.Combat.MessageCenter.PublishMessage(new AddSequenceToStackMessage (new ShowActorInfoSequence(defender, "WOOPS!", FloatieMessage.MessageNature.Debuff, false))); LogReport("Very klutzy!"); return; } } } // store saving throw // check it against panic // check it again ejection var savingThrow = SavingThrows.GetSavingThrow(defender, attacker, heatdamage, damageIncludingHeatDamage); // panic saving throw if (SavingThrows.SavedVsPanic(defender, savingThrow)) { return; } if (!modSettings.OneChangePerTurn) { TurnDamageTracker.resetDamageTrackerFor(defender); } // stop if pilot isn't Panicked if (TrackedActors[index].PanicStatus != PanicStatus.Panicked) { return; } // eject saving throw if (!modSettings.AlwaysPanic && SavingThrows.SavedVsEject(defender, savingThrow)) { return; } // ejecting // random phrase if (modSettings.EnableEjectPhrases && defender is Mech && Random.Range(1, 100) <= modSettings.EjectPhraseChance) { var ejectMessage = ejectPhraseList[Random.Range(0, ejectPhraseList.Count)]; // thank you IRBTModUtils //LogDebug($"defender {defender}"); var castDef = Coordinator.CreateCast(defender); var content = new DialogueContent( ejectMessage, Color.white, castDef.id, null, null, DialogCameraDistance.Medium, DialogCameraHeight.Default, 0 ); content.ContractInitialize(defender.Combat); defender.Combat.MessageCenter.PublishMessage(new PanicSystemDialogMessage(defender, content, 6)); } // remove effects, to prevent exceptions that occur for unknown reasons var combat = UnityGameInstance.BattleTechGame.Combat; var effectsTargeting = combat.EffectManager.GetAllEffectsTargeting(defender); foreach (var effect in effectsTargeting) { // some effects removal throw, so silently drop them try { defender.CancelEffect(effect); } catch { // ignored } } if (modSettings.VehiclesCanPanic && defender is Vehicle v) { // make the regular Pilot Ejected floatie not appear, for this ejection Patches.VehicleRepresentation.supressDeathFloatieOnce(); defender.EjectPilot(defender.GUID, -1, DeathMethod.PilotEjection, true); CastDef castDef = Coordinator.CreateCast(defender); DialogueContent content = new DialogueContent( "Destroy the tech, let's get outta here!", Color.white, castDef.id, null, null, DialogCameraDistance.Medium, DialogCameraHeight.Default, 0 ); content.ContractInitialize(defender.Combat); defender.Combat.MessageCenter.PublishMessage(new PanicSystemDialogMessage(defender, content, 5)); } else { defender.EjectPilot(defender.GUID, -1, DeathMethod.PilotEjection, false); } LogReport("Ejected"); //LogDebug($"Runtime {stopwatch.Elapsed}"); if (!modSettings.CountAsKills) { return; } //handle weird cases due to damage from all sources if (attacker.GUID == defender.GUID) { //killed himself - possibly mines or made a building land on his own head ;) LogReport("Self Kill not counting"); return; } if (attacker.team.GUID == defender.team.GUID) { //killed a friendly LogReport("Friendly Fire, Same Team Kill, not counting"); return; } if (TurnDamageTracker.EjectionAlreadyCounted(defender)) { return; } try { // this seems pretty convoluted var attackerPilot = combat.AllMechs.Where(mech => mech.pilot.Team.IsLocalPlayer) .Where(x => x.PilotableActorDef == attacker.PilotableActorDef).Select(y => y.pilot).FirstOrDefault(); var statCollection = attackerPilot?.StatCollection; if (statCollection == null) { return; } if (defender is Mech) { // add UI icons.. and pilot history? ... MechsKilled already incremented?? // TODO count kills recorded on pilot history so it's not applied twice -added a check above should work unless other mods are directly modifying stats statCollection.Set("MechsKilled", attackerPilot.MechsKilled + 1); var stat = statCollection.GetStatistic("MechsEjected"); if (stat == null) { statCollection.AddStatistic("MechsEjected", 1); } else { var value = stat.Value <int>(); statCollection.Set("MechsEjected", value + 1); } } else if (modSettings.VehiclesCanPanic && defender is Vehicle) { statCollection.Set("OthersKilled", attackerPilot.OthersKilled + 1); var stat = statCollection.GetStatistic("VehiclesEjected"); if (stat == null) { statCollection.AddStatistic("VehiclesEjected", 1); //return; } else { var value = stat.Value <int>(); statCollection.Set("VehiclesEjected", value + 1); } } // add achievement kill (more complicated) var combatProcessors = Traverse.Create(UnityGameInstance.BattleTechGame.Achievements).Field("combatProcessors").GetValue <AchievementProcessor[]>(); var combatProcessor = combatProcessors.FirstOrDefault(x => x.GetType() == AccessTools.TypeByName("BattleTech.Achievements.CombatProcessor")); // field is of type Dictionary<string, CombatProcessor.MechCombatStats> var playerMechStats = Traverse.Create(combatProcessor).Field("playerMechStats").GetValue <IDictionary>(); if (playerMechStats != null) { foreach (DictionaryEntry kvp in playerMechStats) { if ((string)kvp.Key == attackerPilot.GUID) { Traverse.Create(kvp.Value).Method("IncrementKillCount").GetValue(); } } } var r = attackerPilot.StatCollection.GetStatistic("MechsEjected") == null ? 0 : attackerPilot.StatCollection.GetStatistic("MechsEjected").Value <int>(); LogDebug($"{attackerPilot.Callsign} SetMechEjectionCount {r}"); r = attackerPilot.StatCollection.GetStatistic("VehiclesEjected") == null ? 0 : attackerPilot.StatCollection.GetStatistic("VehiclesEjected").Value <int>(); LogDebug($"{attackerPilot.Callsign} SetVehicleEjectionCount {r}"); } catch (Exception ex) { LogDebug(ex); } }
void OnGUI() { if (dialogue == null) { return; } if (!isActive) { if (GUI.Button(new Rect(Screen.width * .5f, Screen.height * .5f, 200, 22), "Start Dialogue")) { isActive = true; dialogueController.startDialogue(this, this); } return; } const float uiRespHeight = 22; const float uiRespBlockHeight = uiRespHeight + 3; const float uiContentHeaderHeight = 20; const float uiContentOffset = uiContentHeaderHeight + 3; const float uiContentHeight = 105; const float uiResponseHeight = 6 * uiRespHeight + 8; const float uiFullHeight = uiContentHeight + uiResponseHeight; const float m = 4; float sw = Screen.width - 2 * m; float sh = Screen.height - m; Rect fullRect = new Rect(m, sh - uiFullHeight, sw, uiFullHeight); DialogueContent content = dialogueController.getCurrentContent(); DialogueResponse[] responses = dialogueController.getCurrentResponses(); string contentTxt = content.text; string charNameTxt = dialogue.characters[content.speakerId].name; GUI.BeginGroup(fullRect); GUI.Box(new Rect(0, 0, fullRect.width, fullRect.height), ""); // Content block: Rect contRect = new Rect(m, m + uiContentOffset, fullRect.width - 2 * m, uiContentHeight); Rect headerRect = new Rect(contRect.x, 0, contRect.width, uiContentHeaderHeight); GUI.Label(headerRect, string.Format("<b>{0}:</b>", charNameTxt)); GUI.Label(contRect, contentTxt); // Response block: Rect respRect = new Rect(contRect.x, fullRect.height - uiResponseHeight - m, contRect.width, uiResponseHeight); GUI.BeginGroup(respRect); // Show all available response options: for (int i = 0; i < responses.Length; ++i) { Rect iRespRect = new Rect(0, i * uiRespBlockHeight, respRect.width, uiRespHeight); DialogueResponse response = responses[i]; string iRespTxt = response.responseText; // Display each response as a simple button: if (GUI.Button(iRespRect, iRespTxt)) { // Call onto dialogue controller to select this response: dialogueController.selectResponse(i); } } GUI.EndGroup(); GUI.EndGroup(); // Quest popup: float questPopupDelta = Time.time - uiQuestPopupTime; const float questPopupMaxTime = 3.0f; if (questPopupDelta < questPopupMaxTime) { Color prevCol = GUI.color; float alpha = 1 - Mathf.Clamp01((questPopupDelta - 1.0f) / (questPopupMaxTime - 1.0f)); GUI.color = new Color(1, 0.75f, 0, alpha); GUI.Box(new Rect(Screen.width * .5f - 180, 128, 360, 60), "New Quest:\n<b>Saving the princess.</b>"); GUI.color = prevCol; } }
public static void Prefix(AttackStackSequence __instance, MessageCenterMessage message) { stopwatch.Restart(); if (ShouldSkipProcessing(__instance, message)) { return; } if (!(message is AttackCompleteMessage attackCompleteMessage)) { return; } var director = __instance.directorSequences; if (director == null) { return; } LogReport(new string('═', 46)); LogReport($"{director[0].attacker.DisplayName} attacks {director[0].chosenTarget.DisplayName}"); // get the attacker in case they have mech quirks AbstractActor defender = null; switch (director[0]?.chosenTarget) { case Vehicle _: defender = (Vehicle)director[0]?.chosenTarget; break; case Mech _: defender = (Mech)director[0]?.chosenTarget; break; } // a building or turret? if (defender == null) { LogDebug("Not a mech or vehicle"); return; } if (defender.IsDead || defender.IsFlaggedForDeath) { return; } var attacker = director[0].attacker; var index = GetActorIndex(defender); if (modSettings.OneChangePerTurn && TrackedActors[index].PanicWorsenedRecently) { LogDebug($"OneChangePerTurn {defender.Nickname} - abort"); return; } if (!modSettings.AlwaysPanic && !ShouldPanic(defender, attackCompleteMessage.attackSequence)) { return; } // automatically eject a klutzy pilot on knockdown with an additional roll failing on 13 if (defender.IsFlaggedForKnockdown) { var defendingMech = (Mech)defender; if (defendingMech.pilot.pilotDef.PilotTags.Contains("pilot_klutz")) { if (Random.Range(1, 100) == 13) { defender.Combat.MessageCenter.PublishMessage(new AddSequenceToStackMessage (new ShowActorInfoSequence(defender, "WOOPS!", FloatieMessage.MessageNature.Debuff, false))); LogReport("Very klutzy!"); return; } } } // store saving throw // check it against panic // check it again ejection var savingThrow = SavingThrows.GetSavingThrow(defender, attacker); Mech_AddExternalHeat_Patch.heatDamage = 0; // panic saving throw if (SavingThrows.SavedVsPanic(defender, savingThrow)) { return; } // stop if pilot isn't Panicked if (TrackedActors[index].PanicStatus != PanicStatus.Panicked) { return; } // eject saving throw if (!modSettings.AlwaysPanic && SavingThrows.SavedVsEject(defender, savingThrow)) { return; } // ejecting // random phrase if (modSettings.EnableEjectPhrases && defender is Mech && Random.Range(1, 100) <= modSettings.EjectPhraseChance) { var ejectMessage = ejectPhraseList[Random.Range(0, ejectPhraseList.Count)]; // thank you IRBTModUtils //LogDebug($"defender {defender}"); var castDef = Coordinator.CreateCast(defender); var content = new DialogueContent( ejectMessage, Color.white, castDef.id, null, null, DialogCameraDistance.Medium, DialogCameraHeight.Default, 0 ); content.ContractInitialize(defender.Combat); defender.Combat.MessageCenter.PublishMessage(new PanicSystemDialogMessage(defender, content, 6)); } // remove effects, to prevent exceptions that occur for unknown reasons var combat = UnityGameInstance.BattleTechGame.Combat; var effectsTargeting = combat.EffectManager.GetAllEffectsTargeting(defender); foreach (var effect in effectsTargeting) { // some effects removal throw, so silently drop them try { defender.CancelEffect(effect); } catch { // ignored } } if (modSettings.VehiclesCanPanic && defender is Vehicle) { // make the regular Pilot Ejected floatie not appear, for this ejection var original = AccessTools.Method(typeof(BattleTech.VehicleRepresentation), "PlayDeathFloatie"); var prefix = AccessTools.Method(typeof(VehicleRepresentation), nameof(VehicleRepresentation.PrefixDeathFloatie)); harmony.Patch(original, new HarmonyMethod(prefix)); defender.EjectPilot(defender.GUID, attackCompleteMessage.stackItemUID, DeathMethod.PilotEjection, true); harmony.Unpatch(original, HarmonyPatchType.Prefix); CastDef castDef = Coordinator.CreateCast(defender); DialogueContent content = new DialogueContent( "Destroy the tech, let's get outta here!", Color.white, castDef.id, null, null, DialogCameraDistance.Medium, DialogCameraHeight.Default, 0 ); content.ContractInitialize(defender.Combat); defender.Combat.MessageCenter.PublishMessage(new PanicSystemDialogMessage(defender, content, 5)); } else { defender.EjectPilot(defender.GUID, attackCompleteMessage.stackItemUID, DeathMethod.PilotEjection, false); } LogReport("Ejected"); //LogDebug($"Runtime {stopwatch.Elapsed}"); if (!modSettings.CountAsKills) { return; } try { // this seems pretty convoluted var attackerPilot = combat.AllMechs.Where(mech => mech.pilot.Team.IsLocalPlayer) .Where(x => x.PilotableActorDef == attacker.PilotableActorDef).Select(y => y.pilot).FirstOrDefault(); var statCollection = attackerPilot?.StatCollection; if (statCollection == null) { return; } if (defender is Mech) { // add UI icons.. and pilot history? ... MechsKilled already incremented?? // TODO count kills recorded on pilot history so it's not applied twice statCollection.Set("MechsKilled", attackerPilot.MechsKilled + 1); var stat = statCollection.GetStatistic("MechsEjected"); if (stat == null) { statCollection.AddStatistic("MechsEjected", 1); return; } var value = stat.Value <int>(); statCollection.Set("MechsEjected", value + 1); } // add achievement kill (more complicated) var combatProcessors = Traverse.Create(UnityGameInstance.BattleTechGame.Achievements).Field("combatProcessors").GetValue <AchievementProcessor[]>(); var combatProcessor = combatProcessors.FirstOrDefault(x => x.GetType() == AccessTools.TypeByName("BattleTech.Achievements.CombatProcessor")); // field is of type Dictionary<string, CombatProcessor.MechCombatStats> var playerMechStats = Traverse.Create(combatProcessor).Field("playerMechStats").GetValue <IDictionary>(); if (playerMechStats != null) { foreach (DictionaryEntry kvp in playerMechStats) { if ((string)kvp.Key == attackerPilot.GUID) { Traverse.Create(kvp.Value).Method("IncrementKillCount").GetValue(); } } } else if (modSettings.VehiclesCanPanic && defender is Vehicle) { var stat = statCollection.GetStatistic("VehiclesEjected"); if (stat == null) { statCollection.AddStatistic("VehiclesEjected", 1); return; } var value = stat.Value <int>(); statCollection.Set("VehiclesEjected", value + 1); } } catch (Exception ex) { LogDebug(ex); } }