public void Tick() { QuestObjectState state; bool result; var values = m_ActivelyChecked.Values; IQuestSystemObject[] objects = new IQuestSystemObject[values.Count]; values.CopyTo(objects, 0); GameDebugger.Log(LogLevel.Debug, "QS: TICK"); foreach (IQuestSystemObject obj in objects) { state = GetState(obj); if (state.Locked) { GameDebugger.Log(LogLevel.Warning, "QS: {0} '0x{1:X16}' is in the list of actively checked objects while being locked", obj.GetType().Name, obj.Id); m_ActivelyChecked.Remove(obj.Id); continue; } GameDebugger.Log(LogLevel.Debug, "QS: Checking {0} '0x{1:X16}'", obj.GetType().Name, obj.Id); if (obj is QuestPin) { QuestPin pin = (QuestPin)obj; result = CheckCondition(pin, true); if (result && state.Active == QuestObjectState.ActivationState.PartiallyActive) { state.Active = QuestObjectState.ActivationState.Active; GameDebugger.Log(LogLevel.Debug, "QS: Switched {0} '0x{1:X16}' from partially activated to activated", obj.GetType().Name, obj.Id); if (pin.Node is Quest) { Activate((BaseQuestObject)pin.Node); } else { CheckInputsOnActivation((BaseQuestObject)pin.Node); } foreach (QuestConnection connection in pin.Connections) { if (connection.Source == pin) { Activate(connection); } } } else if (!result && state.Active == QuestObjectState.ActivationState.Active) { state.Active = QuestObjectState.ActivationState.PartiallyActive; GameDebugger.Log(LogLevel.Debug, "QS: Switched {0} '0x{1:X16}' from activated to partially activated", obj.GetType().Name, obj.Id); Deactivate((BaseQuestObject)pin.Node); foreach (QuestConnection connection in pin.Connections) { if (connection.Source == pin) { Deactivate(connection); } } } } else if (obj is QuestCondition) { // every tick depending on it's script execution result it switches active either output[0] or output[1] active QuestCondition condition = (QuestCondition)obj; result = CheckCondition(condition, true); if (result) { if (state.CompletionOutput != 0 || state.Completion == QuestObjectState.CompletionState.Incomplete) { if (state.Completion != QuestObjectState.CompletionState.Incomplete) { Deactivate((QuestPin)condition.Outputs[1]); GameDebugger.Log(LogLevel.Debug, "QS: Switching {0} '0x{1:X16}' output from FALSE to TRUE", obj.GetType().Name, obj.Id); } else { GameDebugger.Log(LogLevel.Debug, "QS: Setting {0} '0x{1:X16}' output to TRUE", obj.GetType().Name, obj.Id); } Activate((QuestPin)condition.Outputs[0]); state.CompletionOutput = 0; } } else { if (state.CompletionOutput != 1 || state.Completion == QuestObjectState.CompletionState.Incomplete) { if (state.Completion != QuestObjectState.CompletionState.Incomplete) { Deactivate((QuestPin)condition.Outputs[0]); GameDebugger.Log(LogLevel.Debug, "QS: Switching {0} '0x{1:X16}' output from TRUE to FALSE", obj.GetType().Name, obj.Id); } else { GameDebugger.Log(LogLevel.Debug, "QS: Setting {0} '0x{1:X16}' output to FALSE", obj.GetType().Name, obj.Id); } Activate((QuestPin)condition.Outputs[1]); state.CompletionOutput = 1; } } state.Completion = QuestObjectState.CompletionState.Successful; } else if (obj is QuestObjective) { // every tick rechecks all conditions on output pins QuestObjective objective = (QuestObjective)obj; int pinIndex = 0; foreach (QuestPin pin in objective.Outputs) { if (pin.Script == null) { pinIndex++; continue; } result = CheckCondition(pin, false); if (result) { Activate(pin); // this will also deactivate previously active pin break; // Just in case when there is more than one output that passes the check. We don't want lots of unneeded events, right? } if (pinIndex == state.CompletionOutput && state.Completion != QuestObjectState.CompletionState.Incomplete) { state.Completion = QuestObjectState.CompletionState.Incomplete; GameDebugger.Log(LogLevel.Debug, "QS: event: OnQuestObjectiveIncomplete"); if (OnQuestObjectiveIncomplete != null) { OnQuestObjectiveIncomplete.Invoke(new QuestObjectiveIncompleteEventArgs(objective)); } } Deactivate(pin); pinIndex++; } } else { GameDebugger.Log(LogLevel.Warning, "QS: {0} '0x{1:X16}' is not supposed to be in the list of actively checked objects", obj.GetType().Name, obj.Id); } } }
public static bool ImportArticyQuests(Project project, string questContainerExternalId) { ArticyFlowObject questContainer; try { questContainer = project.GetFlowObject(questContainerExternalId); } catch { throw new UserFriendlyException( String.Format("Could not find quest container '{0}' in the project.", questContainerExternalId), "One of data files does not contain required information." ); } IList <GraphNode> quests = questContainer.Children; GameDebugger.Log(LogLevel.Debug, "{0} quests found:", quests.Count); ArticyFlowFragment articyQuest; Quest quest; Dictionary <ulong, QuestPin> pinIndex = new Dictionary <ulong, QuestPin>(); List <Jump> articyJumps = new List <Jump>(); bool badScripts = false; // copy quests foreach (GraphNode node in questContainer.Children) { // hubs are used to join quests for further story progression: hub remains inactive until all quests connected to the hub are complete if (node is ArticyHub) { ArticyHub articyHub = (ArticyHub)node; QuestHub hub = new QuestHub(articyHub.Id, articyHub.ExternalId); badScripts |= ImportArticyPins(articyHub, hub, true, true, pinIndex); GameDebugger.Log(LogLevel.Debug, "Adding quest hub '0x{0:X16}'", hub.Id); Add(hub); } // flow fragments are quests if (node is ArticyFlowFragment) { articyQuest = (ArticyFlowFragment)node; quest = new Quest(articyQuest.Id, articyQuest.ExternalId); GameDebugger.Log(LogLevel.Debug, "Importing quest '{0}'", quest.Key); GameDebugger.Log(LogLevel.Debug, "Quest has {0} inputs and {1} outputs", articyQuest.Inputs.Count, articyQuest.Outputs.Count); badScripts |= ImportArticyPins(articyQuest, quest, true, true, pinIndex); quest.Name = articyQuest.DisplayName; quest.Description = articyQuest.Text; Add(quest); // copy quest fragments foreach (GraphNode childNode in node.Children) { if (childNode is ArticyDialogue || childNode is ArticyDialogueFragment) { throw new UserFriendlyException( String.Format("Dialogue or dialogue fragment '0x{0:X16}' is not supposed to be a part of a quest", ((ArticyFlowFragment)childNode).Id), "One of data files is either corupt or has a version that is not supported by the game engine." ); } if (childNode is ArticyFlowFragment) { ArticyFlowFragment articyFragment = (ArticyFlowFragment)childNode; if (string.IsNullOrWhiteSpace(articyFragment.DisplayName)) { QuestLogicGate gate = new QuestLogicGate(articyFragment.Id, articyFragment.ExternalId); badScripts |= ImportArticyPins(articyFragment, gate, true, true, pinIndex); GameDebugger.Log(LogLevel.Debug, "Adding logic gate '0x{0:X16}' to the quest", gate.Id); quest.AddChild(gate); Add(gate); } else { QuestObjective objective = new QuestObjective(articyFragment.Id, articyFragment.ExternalId); objective.Description = articyFragment.Text; badScripts |= ImportArticyPins(articyFragment, objective, true, true, pinIndex); GameDebugger.Log(LogLevel.Debug, "Adding objective '0x{0:X16}' to the quest", objective.Id); objective.Name = articyFragment.DisplayName; objective.Description = articyFragment.Text; quest.AddChild(objective); Add(objective); } } if (childNode is ArticyCondition) { ArticyCondition articyCondition = (ArticyCondition)childNode; QuestCondition condition = new QuestCondition(articyCondition.Id, articyCondition.ExternalId); if (string.IsNullOrWhiteSpace(articyCondition.Script)) { badScripts = true; GameDebugger.Log(LogLevel.Error, "Condition '0x{0:X16}' has no script", condition.Id); } else { if ((condition.Script = CompileScript(articyCondition.Script, Quests.ConditionScriptEnvironment, "condition", condition.Id)) == null) { badScripts = true; } } badScripts |= ImportArticyPins(articyCondition, condition, true, true, pinIndex); GameDebugger.Log(LogLevel.Debug, "Adding condition '0x{0:X16}' to the quest", condition.Id); quest.AddChild(condition); Add(condition); } if (childNode is ArticyInstruction) { ArticyInstruction articyInstruction = (ArticyInstruction)childNode; QuestInstruction instruction = new QuestInstruction(articyInstruction.Id, articyInstruction.ExternalId); if (string.IsNullOrWhiteSpace(articyInstruction.Script)) { GameDebugger.Log(LogLevel.Notice, "Instruction '0x{0:X16}' has no script", instruction.Id); } else { if ((instruction.Script = CompileScript(articyInstruction.Script, Quests.ConditionScriptEnvironment, "instruction", instruction.Id)) == null) { badScripts = true; } } badScripts |= ImportArticyPins(articyInstruction, instruction, true, true, pinIndex); GameDebugger.Log(LogLevel.Debug, "Adding instruction '0x{0:X16}' to the quest", instruction.Id); quest.AddChild(instruction); Add(instruction); } if (childNode is ArticyHub) { ArticyHub articyHub = (ArticyHub)childNode; QuestSavePoint savePoint = new QuestSavePoint(articyHub.Id, articyHub.ExternalId); badScripts |= ImportArticyPins(articyHub, savePoint, true, true, pinIndex); GameDebugger.Log(LogLevel.Debug, "Adding save point '0x{0:X16}' to the quest", savePoint.Id); quest.AddChild(savePoint); Add(savePoint); } if (childNode is Jump) { articyJumps.Add((Jump)childNode); } } } } // jumps need postprocessing, because we must make sure all quest system nodes are already added // jumps must be treated as connections. they must be disallowed to connect to anything outside the scope of current quest. BaseQuestObject obj; QuestPin srcPin, dstPin; IList <GraphPin> inputs; foreach (Jump articyJump in articyJumps) { GameDebugger.Log(LogLevel.Debug, "Linking jump '0x{0:X16}'", articyJump.Id); if (articyJump.TargetPin != null) { if (!pinIndex.TryGetValue(articyJump.TargetPin.Id, out dstPin)) { GameDebugger.Log(LogLevel.Debug, "Ignoring jump '0x{0:X16}' since it's target pin is outside the quest container", articyJump.Id); continue; } } else if (articyJump.Target != null) { inputs = articyJump.Target.Inputs; if (inputs.Count == 0) { GameDebugger.Log(LogLevel.Debug, "Ignoring jump '0x{0:X16}' since it's target does not have input pins", articyJump.Id); continue; } if (!pinIndex.TryGetValue(((FlowObjectPin)inputs[0]).Id, out dstPin)) { GameDebugger.Log(LogLevel.Debug, "Ignoring jump '0x{0:X16}' since it's target is outside the quest container", articyJump.Id); continue; } } else { continue; } obj = (BaseQuestObject)dstPin.Node; if (obj.Parent == null || !(obj.Parent is Quest) || ((Quest)obj.Parent).Id != ((ArticyFlowObject)articyJump.Parent).Id) { GameDebugger.Log(LogLevel.Debug, "Ignoring jump '0x{0:X16}' since it's not targetting an object within the same quest", articyJump.Id); continue; } foreach (FlowObjectPin pin in articyJump.Inputs) { foreach (ArticyFlowConnection articyConnection in pin.Connections) { if (articyConnection.Target != pin) { continue; } if (!pinIndex.TryGetValue(((FlowObjectPin)articyConnection.Source).Id, out srcPin)) { continue; } QuestConnection newConnection = new QuestConnection(articyConnection.Id, articyConnection.ExternalId); newConnection.Source = srcPin; newConnection.Target = dstPin; GameDebugger.Log(LogLevel.Debug, "Connected '0x{0:X16}' to '0x{1:X16}' (jump replacement)", ((BaseQuestObject)newConnection.Source.Node).Id, ((BaseQuestObject)newConnection.Target.Node).Id); } } } // recreate connections foreach (ArticyFlowConnection articyConnection in project.FlowConnections) { if (!pinIndex.TryGetValue(((FlowObjectPin)articyConnection.Target).Id, out dstPin)) { continue; } if (dstPin.Type == GraphPin.PinType.Input && dstPin.Node is Quest && articyConnection.Source.Node == questContainer) { if (!m_InitiallyActiveQuestPins.Contains(dstPin)) { m_InitiallyActiveQuestPins.Add(dstPin); } GameDebugger.Log(LogLevel.Debug, "Adding pin '0x{0:X16}' to list of initially active quest pins", dstPin.Id); continue; } if (!pinIndex.TryGetValue(((FlowObjectPin)articyConnection.Source).Id, out srcPin)) { continue; } QuestConnection newConnection = new QuestConnection(articyConnection.Id, articyConnection.ExternalId); newConnection.Source = srcPin; newConnection.Target = dstPin; GameDebugger.Log(LogLevel.Debug, "Connected '0x{0:X16}' to '0x{1:X16}'", ((BaseQuestObject)newConnection.Source.Node).Id, ((BaseQuestObject)newConnection.Target.Node).Id); } return(badScripts); }