Skip to content

Commit

Permalink
Merge pull request #55 from CWolfs/develop
Browse files Browse the repository at this point in the history
v0.5.0
  • Loading branch information
CWolfs authored Jun 2, 2023
2 parents f892afb + 7c4649f commit 8d2493e
Show file tree
Hide file tree
Showing 22 changed files with 467 additions and 26 deletions.
7 changes: 6 additions & 1 deletion .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,14 @@
"type": "process",
"args": [
"build",
"E:/modding/ExtendedConversations/src/ExtendedConversations.csproj"
"E:/Modding/BattleTech/ExtendedConversations/src/ExtendedConversations.csproj"
],
"problemMatcher": "$msCompile"
},
{
"label": "copy (assets)",
"type": "shell",
"command": "./copy-assets.sh"
}
]
}
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ Dialog tags allow you to inject data into your dialog text.
- `Set Characters Visible` - This allows you to show and hide characters in the dropship. You specify the characters in a comma separated list.
- `Add Contract` - This allows you to add a contract to the contracts list in the XOs room.
- `Add Flashpoint` - This allows you to add a flashpoint to the starmap. See [usage instructions](https://github.com/CWolfs/ExtendedConversations/issues/44#issuecomment-1335134292).
- `Set BattleTech Camera Hard Lock` - This has one advantage over the vanilla `Set BattleTech Camera Lock`. It is useful in 1-on-1 conversations and letting other characters talk without transitioning away. The vanilla lock doesn't work for 1-on-1 conversations.
- `Sideload Conversation` - This loads another conversation into the current active conversation. It's seamless so you it feels like it's the same conversation. It can enter the new conversation at a specific node and can also return to the original conversation if enabled. **Important: It's recommended you use this on Response nodes, but you can use it on Prompt nodes too. When using on Prompt nodes conditionals for the following responses at that specific level might not work**.

### Value Getters

Expand Down
4 changes: 4 additions & 0 deletions copy-assets.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/bin/bash
cp -r 'mod.json' 'D:/Program Files (x86)/Steam/steamapps/common/BATTLETECH/Mods/ExtendedConversations'
cp -r 'src/bin/Debug/net471/ExtendedConversations.dll' 'D:/Program Files (x86)/Steam/steamapps/common/BATTLETECH/Mods/ExtendedConversations'
cp -r 'src/bin/Debug/net471/ExtendedConversations.pdb' 'D:/Program Files (x86)/Steam/steamapps/common/BATTLETECH/Mods/ExtendedConversations'
2 changes: 1 addition & 1 deletion mod.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"Name": "ExtendedConversations",
"Enabled": true,

"Version": "0.4.5",
"Version": "0.5.0",
"Description": "Extended Conversations is a utility mod to provide more conversation conditions, actions and other functionality",
"Author": "CWolf",
"Website": "https://github.com/CWolfs/ExtendedConversations/",
Expand Down
35 changes: 35 additions & 0 deletions operations/set_battletech_camera_hard_lock.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"key": "Set BattleTech Camera Hard Lock",
"label": "Hard lock the camera to",
"view": ["label", "inputs"],
"scope": "action",
"category": "primary",
"tooltip": "This will ensure only the set lock target is shown. Mainly useful for 1-on-1 conversations and having the non-main character talk but the camera stay focused on the main character.",
"inputs": [
{
"label": "Key",
"types": ["string"],
"values": [
{ "text": "AUTO", "value": "AUTO" },
{ "text": "Alexander", "value": "ALEXANDER" },
{ "text": "Darius", "value": "DARIUS" },
{ "text": "Farah", "value": "FARAH" },
{ "text": "Kamea", "value": "KAMEA" },
{ "text": "Sumire", "value": "SUMIRE" },
{ "text": "Yang", "value": "YANG" },
{ "text": "Monitor", "value": "MONITOR" },
{ "text": "Navscreen", "value": "NAVSCREEN" },
{ "text": "Default", "value": "DEFAULT" },
{ "text": "Contracts", "value": "CONTRACTS" },
{ "text": "Mechwarriors", "value": "MECHWARRIORS" },
{ "text": "Memorial", "value": "MEMORIAL" },
{ "text": "Argo Upgrade", "value": "ARGOUPGRADE" },
{ "text": "Mechlab", "value": "MECHLAB" },
{ "text": "Breakdown", "value": "BREAKDOWN" },
{ "text": "Commander", "value": "COMMANDER" },
{ "text": "Hologram", "value": "HOLOGRAM" },
{ "text": "Heraldry", "value": "HERALDRY" }
]
}
]
}
31 changes: 31 additions & 0 deletions operations/sideload_conversation.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"key": "Sideload Conversation",
"label": "Sideload conversation",
"view": ["label", "inputs"],
"scope": "action",
"category": "primary",
"tooltip": "This will sideload a conversation into an already running conversation. Useful for having a top level conversation that loads others based on high level conditions. Important: Its recommeneded you set this action on Responses. If you set on a Prompt node then the following Response might not evaluate its condition but it will work for sideloading.",
"inputs": [
{
"label": "Conversation Id",
"types": ["string"],
"viewLabel": "for conversation id {value}"
},
{
"label": "Entry Node Id",
"types": ["string"],
"tooltip": "The Prompt / Response node to sideload directly into the conversation on. If left blank then it will enter the conversation like normal (and evalute all conditions on root nodes).",
"viewLabel": "and enter at node id {value}"
},
{
"label": "Resume host after sideload finished",
"types": ["int"],
"tooltip": "This setting can allow the conversation to resume from this point after the sideloaded conversation has finished.",
"values": [
{ "viewlabel": "", "text": "False", "value": 0 },
{ "viewlabel": "and resume the host conversation on finish", "text": "True", "value": 1 }
],
"defaultValue": 1
}
]
}
2 changes: 1 addition & 1 deletion operations/start_conversation.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
"value": 1
}
],
"defaultValue": 1
"defaultValue": 0
}
]
}
140 changes: 139 additions & 1 deletion src/Core/Actions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,24 @@

using ExtendedConversations;
using ExtendedConversations.Utils;
using ExtendedConversations.State;

namespace ExtendedConversations.Core {
public class Actions {
public static bool IsNodeAction { get; set; } = false;
public static bool IsLinkAction { get; set; } = false; // Response or Root

// TODO: Move all this into a state class
public static bool MovedKameraInLeopardCommandCenter = false;
public static bool ForceNextIsInFlashpointCheckFalse = false;
public static Conversation ActiveConversation = null;
public static string HardLockTarget = null;

// Sideload Conversation state
public static bool ReplaceLinkOnResponseIfNeeded { get; set; } = false;
public static bool SideLoadCaptureNextResponseIndex { get; set; } = false;
public static Dictionary<string, SideloadConversationState> SideLoadCachedState = new Dictionary<string, SideloadConversationState>();
public static Dictionary<string, string> SideloadConversationMap = new Dictionary<string, string>(); // <sideloadedConvoId, previousConvoId>

public static object TimeSkip(TsEnvironment env, object[] inputs) {
int daysToSkip = env.ToInt(inputs[0]);
Expand Down Expand Up @@ -141,10 +153,125 @@ public static object StartConversation(TsEnvironment env, object[] inputs) {

static IEnumerator WaitThenQueueConversation(SimGameState simulation, Conversation conversation, string groupHeader, string groupSubHeader) {
yield return new WaitForSeconds(1);
SimGameInterruptManager interruptManager = (SimGameInterruptManager)ReflectionHelper.GetPrivateField(simulation, "interruptQueue");
SimGameInterruptManager interruptManager = simulation.interruptQueue;
interruptManager.QueueConversation(conversation, groupHeader, groupSubHeader, null, true);
}

public static object SideloadConversation(TsEnvironment env, object[] inputs) {
string conversationId = env.ToString(inputs[0]);
string nodeEntryId = env.ToString(inputs[1]);
bool resumeHostOnFinish = env.ToBool(inputs[2]);
Main.Logger.Log($"[SideloadConversation] Sideload conversation id: " + conversationId + " with nodeEntryId: " + nodeEntryId + " with resumeHostOnFinish: " + resumeHostOnFinish);
if (IsNodeAction) Main.Logger.Log($"[SideloadConversation] Sideload conversation is a node action");
if (IsLinkAction) Main.Logger.Log($"[SideloadConversation] Sideload conversation is a link action");
ReplaceLinkOnResponseIfNeeded = true;

Conversation conversation = null;
SimGameState simGame = UnityGameInstance.Instance.Game.Simulation;
SimGameConversationManager conversationManager = simGame.ConversationManager;
Conversation currentConversation = conversationManager.thisConvoDef;

try {
conversation = simGame.DataManager.SimGameConversations.Get(conversationId);
} catch (KeyNotFoundException) {
Main.Logger.Log($"[SideloadConversation] Conversation with id '{conversationId}' not found. Check the conversation id is correct or/and if the conversation has loaded correctly.");
}

if (conversation == null) {
Main.Logger.Log($"[SideloadConversation] Conversation is null for id '{conversationId}'");
} else {
if (resumeHostOnFinish) {
SideloadConversationState cachedState = new SideloadConversationState();
cachedState.convoDef = currentConversation;
cachedState.currentLink = conversationManager.currentLink;
cachedState.currentNode = conversationManager.currentNode;
cachedState.state = conversationManager.thisState;
cachedState.linkToAutoFollow = conversationManager.linkToAutoFollow;
cachedState.onlyOnceLinks = conversationManager.onlyOnceLinks;

// Handle action being on a Response
if (IsLinkAction) {
Main.Logger.Log($"[SideloadConversation] Is link action - use hydrate node instead");
cachedState.useNodeOnHydrate = true;

bool isNodeInAutofollowMode = false;
bool forceEnd = conversationManager.EvaluateNode(conversationManager.currentNode, out isNodeInAutofollowMode);
Main.Logger.Log("[SideloadConversation] Current node is in autofollow response mode? " + isNodeInAutofollowMode);

if (isNodeInAutofollowMode) {
// Handle action being on an 'Autofollow Response'
for (int i = 0; i < conversationManager.currentNode.branches.Count; i++) {
ConversationLink conversationLink = conversationManager.currentNode.branches[i];
if (conversationLink.responseText == "") {
cachedState.nextNodeIndex = conversationLink.nextNodeIndex;
}
}
} else {
// Handle action being on a 'Text Response'
cachedState.nextNodeIndex = conversationManager.currentLink.nextNodeIndex;
}
}

cachedState.previousNodes = new List<ConversationNode>();
foreach (ConversationNode prevNode in conversationManager.previousNodes) {
cachedState.previousNodes.Add(prevNode);
}

Actions.SideLoadCachedState.Add(currentConversation.idRef.id, cachedState);
Actions.SideloadConversationMap.Add(conversation.idRef.id, currentConversation.idRef.id);

SideLoadCaptureNextResponseIndex = true;
}

conversationManager.thisConvoDef = conversation;
conversationManager.currentNode = null;
conversationManager.currentLink = null;
conversationManager.previousNodes.Clear();

ConversationNode conversationNode = new ConversationNode();
conversationNode.index = -1;
for (int i = 0; i < conversation.roots.Count; i++) {
conversationNode.branches.Add(conversation.roots[i]);
}
conversationManager.currentNode = conversationNode;

bool autoFollow = false;
bool passedConditions = conversationManager.EvaluateNode(conversationNode, out autoFollow);
if (autoFollow && passedConditions) {
conversationManager.EndConversation();
}

// Find the entry node if provided
if (nodeEntryId == "") {
conversationManager.thisState = BattleTech.SimGameConversationManager.ConversationState.NODE;
} else {
int entryNodeIndex = conversation.nodes.FindIndex((node => node.idRef.id == nodeEntryId));

if (entryNodeIndex != -1) {
conversationManager.thisState = BattleTech.SimGameConversationManager.ConversationState.RESPONSE;

ConversationLink conversationLink = new ConversationLink();
conversationLink.onlyOnce = false;
conversationLink.idRef = new IDRef();
conversationLink.idRef.id = Guid.NewGuid().ToString();
conversationLink.nextNodeIndex = entryNodeIndex;

conversationManager.currentLink = conversationLink;
} else {
ConversationLink rootLinkToFollow = conversation.GetRootToFollow();
if (rootLinkToFollow == null) rootLinkToFollow = conversation.roots[0];
conversationManager.currentLink = rootLinkToFollow;
conversationManager.linkToAutoFollow = 0;
}
}
}

IsNodeAction = false;
IsLinkAction = false;

return null;
}

public static object AddContract(TsEnvironment env, object[] inputs) {
string contractId = env.ToString(inputs[0]);
string target = env.ToString(inputs[1]);
Expand Down Expand Up @@ -199,5 +326,16 @@ public static object AddFlashpoint(TsEnvironment env, object[] inputs) {

return null;
}

public static object SetCameraHardLock(TsEnvironment env, object[] inputs) {
string key = env.ToString(inputs[0]);

Main.Logger.Log($"[SetCameraHardLock] Received key '{key}'");
HardLockTarget = key;

UnityGameInstance.BattleTechGame.Simulation.ConversationManager.SetCameraLockTarget(key);

return null;
}
}
}
13 changes: 13 additions & 0 deletions src/Core/ConversationUpgrades.cs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,14 @@ public static void Declare(TsEnvironment env) {
tsOp.DeclareInput("conversationId", stringType);
tsOp.DeclareInput("groupHeader", stringType);
tsOp.DeclareInput("groupSubHeader", stringType);
tsOp.DeclareInput("forceNonFPConferenceRoom", intType);

// 'Sideload Conversation' action
Main.Logger.Log("Declaring 'Sideload Conversation' action");
tsOp = env.DeclareOp("EffectFunctions", "Sideload Conversation", voidType, new TsOp.EvalDelegate(Actions.SideloadConversation));
tsOp.DeclareInput("conversationId", stringType);
tsOp.DeclareInput("nodeEntryId", stringType);
tsOp.DeclareInput("resumeHostOnFinish", intType);

// 'Add Contract' action
Main.Logger.Log("Declaring 'Add Contract' action");
Expand All @@ -113,6 +121,11 @@ public static void Declare(TsEnvironment env) {
tsOp.DeclareInput("flashpointId", stringType);
tsOp.DeclareInput("systemId", stringType);

// 'Set BattleTech Camera Hard Lock' action
Main.Logger.Log("Declaring 'Set BattleTech Camera Hard Lock' action");
tsOp = env.DeclareOp("EffectFunctions", "Set BattleTech Camera Hard Lock", voidType, new TsOp.EvalDelegate(Actions.SetCameraHardLock));
tsOp.DeclareInput("key", stringType);

/*
* VALUE GETTERS
*/
Expand Down
15 changes: 13 additions & 2 deletions src/ExtendedConversations.csproj
Original file line number Diff line number Diff line change
@@ -1,13 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Version>0.4.5</Version>
<Version>0.5.0</Version>
<OutputType>Library</OutputType>
<TargetFramework>net471</TargetFramework>
</PropertyGroup>

<PropertyGroup>
<!-- avoids IgnoresAccessChecksToAttribute warnings -->
<PublicizerRuntimeStrategies>Unsafe</PublicizerRuntimeStrategies>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Krafs.Publicizer" Version="2.2.1" />
<Publicize Include="Assembly-CSharp" />
<Publicize Include="DOTween;DOTweenPro" />
<Publicize Include="UnityEngine.CoreModule" />
</ItemGroup>

<ItemGroup>
<Reference Include="0Harmony">
<HintPath>D:\Program Files (x86)\Steam\steamapps\common\BATTLETECH\Mods\ModTek\0Harmony.dll</HintPath>
<HintPath>D:\Program Files (x86)\Steam\steamapps\common\BATTLETECH\BattleTech_Data\Managed\0Harmony.dll</HintPath>
</Reference>
<Reference Include="UnityEngine">
<HintPath>D:\Program Files (x86)\Steam\steamapps\common\BATTLETECH\BattleTech_Data\Managed\UnityEngine.dll</HintPath>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using Harmony;

using BattleTech;

using isogame;

using ExtendedConversations.Core;

namespace ExtendedConversations {
[HarmonyPatch(typeof(SimGameConversationManager), "DoLinkActions")]
public class SimGameConversationManagerDoLinkActionsPatch {
static void Prefix(SimGameConversationManager __instance) {
Actions.IsLinkAction = true;
Actions.IsNodeAction = false;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using Harmony;

using BattleTech;

using ExtendedConversations.Core;

namespace ExtendedConversations {
[HarmonyPatch(typeof(SimGameConversationManager), "DoNodeActions")]
public class SimGameConversationManagerDoNodeActionsPatch {
static void Prefix(SimGameConversationManager __instance) {
Actions.IsLinkAction = false;
Actions.IsNodeAction = true;
}
}
}
Loading

0 comments on commit 8d2493e

Please sign in to comment.