using OpenTheBox.Core;
using OpenTheBox.Core.Enums;
using OpenTheBox.Core.Interactions;
using OpenTheBox.Core.Items;
using OpenTheBox.Data;
using OpenTheBox.Simulation.Events;
namespace OpenTheBox.Simulation;
///
/// Evaluates interaction rules against newly received items and the current game state,
/// triggering automatic interactions or requesting player choices when multiple apply.
///
public class InteractionEngine(ContentRegistry registry)
{
///
/// Checks all interaction rules against newly received items and returns events for
/// any auto-activations or choice prompts.
///
public List CheckAutoActivations(List newItems, GameState state)
{
var events = new List();
foreach (var newItem in newItems)
{
var itemDef = registry.GetItem(newItem.DefinitionId);
if (itemDef is null)
continue;
var matchingRules = FindMatchingRules(itemDef, state);
if (matchingRules.Count == 0)
{
// Special case: key without a matching openable item injects a box into future loot tables
if (itemDef.Tags.Contains("Key"))
{
var hasMatchingOpenable = state.Inventory.Any(i =>
{
var def = registry.GetItem(i.DefinitionId);
return def is not null && def.Tags.Contains("Openable");
});
if (!hasMatchingOpenable)
{
events.Add(new LootTableModifiedEvent(
BoxId: "starter_box",
AddedEntryId: newItem.DefinitionId,
Reason: $"Key '{newItem.DefinitionId}' has no matching Openable item; injecting into future loot tables"
));
}
}
continue;
}
var automaticRules = matchingRules
.Where(r => r.IsAutomatic)
.OrderByDescending(r => r.Priority)
.ToList();
if (automaticRules.Count == 1)
{
// Single automatic match: auto-execute
var rule = automaticRules[0];
events.AddRange(ExecuteRule(rule, newItem, state));
}
else if (automaticRules.Count > 1)
{
// Multiple automatic matches: let the player choose
events.Add(new ChoiceRequiredEvent(
Prompt: $"Multiple interactions available for '{newItem.DefinitionId}'",
Options: automaticRules.Select(r => r.Id).ToList()
));
}
else
{
// Non-automatic rules found but none are automatic -- no auto-activation
}
}
return events;
}
///
/// Finds all interaction rules that match the given item definition and game state.
///
private List FindMatchingRules(ItemDefinition itemDef, GameState state)
{
var matching = new List();
foreach (var rule in registry.InteractionRules)
{
// Check required tags
var tagsMatch = rule.RequiredItemTags.All(tag => itemDef.Tags.Contains(tag));
if (!tagsMatch)
continue;
// Check required item ids (if specified, at least one must be in inventory)
if (rule.RequiredItemIds is not null && rule.RequiredItemIds.Count > 0)
{
var hasRequiredItem = rule.RequiredItemIds.All(id => state.HasItem(id));
if (!hasRequiredItem)
continue;
}
matching.Add(rule);
}
return matching;
}
///
/// Executes a single interaction rule, consuming the trigger item and producing results.
///
private List ExecuteRule(InteractionRule rule, ItemInstance triggerItem, GameState state)
{
var events = new List();
// Consume the trigger item
state.RemoveItem(triggerItem.Id);
events.Add(new ItemConsumedEvent(triggerItem.Id));
// Produce result items if ResultData specifies item definition ids (comma-separated)
if (rule.ResultData is not null)
{
var resultItemIds = rule.ResultData.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
foreach (var resultItemId in resultItemIds)
{
var resultInstance = ItemInstance.Create(resultItemId);
state.AddItem(resultInstance);
events.Add(new ItemReceivedEvent(resultInstance));
}
}
events.Add(new InteractionTriggeredEvent(rule.Id, rule.DescriptionKey));
return events;
}
}