feat: printing pod API + research completion events
Printing Pod:
- GET /api/state/printing_pod: check if ready, options available, cycles remaining
- POST /api/action/printing_pod_select {index}: select option 0/1/2
Research completion tracking:
- Automatic detection of newly completed techs on each state poll
- Events pushed to stream: 'research_complete' with unlocked buildings list
- Events pushed when Printing Pod becomes ready
Event daemon enhanced:
- Classifies 'research complete' events with actionable next steps
- Classifies 'printing pod' events with option viewing instructions
CLI: printing_pod, printing_pod_select <0|1|2>
This commit is contained in:
@ -20,6 +20,10 @@ namespace ONIAgentBridge
|
||||
private static int _eventSeq = 0;
|
||||
private static object _eventLock = new object();
|
||||
|
||||
// Research completion tracking
|
||||
private static HashSet<string> _lastCompletedTechs = new HashSet<string>();
|
||||
private static int _lastPrintingPodCycle = -1;
|
||||
|
||||
public override void OnLoad(Harmony harmony)
|
||||
{
|
||||
base.OnLoad(harmony);
|
||||
@ -155,6 +159,9 @@ namespace ONIAgentBridge
|
||||
case ("/api/state/buildable", "GET"):
|
||||
responseJson = GetBuildable();
|
||||
break;
|
||||
case ("/api/state/printing_pod", "GET"):
|
||||
responseJson = GetPrintingPod();
|
||||
break;
|
||||
|
||||
// --- Cell-level map data ---
|
||||
case ("/api/state/cell", "GET"):
|
||||
@ -278,6 +285,9 @@ namespace ONIAgentBridge
|
||||
case ("/api/action/set_automation", "POST"):
|
||||
responseJson = ExecuteSetAutomation(ctx);
|
||||
break;
|
||||
case ("/api/action/printing_pod_select", "POST"):
|
||||
responseJson = ExecutePrintingPodSelect(ctx);
|
||||
break;
|
||||
|
||||
default:
|
||||
ctx.Response.StatusCode = 404;
|
||||
@ -390,6 +400,50 @@ namespace ONIAgentBridge
|
||||
|
||||
PushEvent("state_poll", "info", "Game state polled", $"Cycle {GameClock.Instance?.GetCycle() ?? 0}");
|
||||
|
||||
// Track research completion
|
||||
try
|
||||
{
|
||||
var currentCompleted = new HashSet<string>();
|
||||
foreach (var tech in Research.Instance?.GetResearchTechnologies() ?? new List<Tech>())
|
||||
{
|
||||
if (tech.IsComplete()) currentCompleted.Add(tech.Id);
|
||||
}
|
||||
foreach (var completed in currentCompleted)
|
||||
{
|
||||
if (!_lastCompletedTechs.Contains(completed))
|
||||
{
|
||||
var t = Research.Instance?.GetResearchTechnologies()
|
||||
.FirstOrDefault(tech => tech.Id == completed);
|
||||
string name = t?.Name ?? completed;
|
||||
string unlocks = "";
|
||||
if (t?.unlockedBuildings != null)
|
||||
unlocks = string.Join(", ", t.unlockedBuildings.Take(5));
|
||||
PushEvent("research_complete", "info", $"Research completed: {name}",
|
||||
$"Unlocked: {unlocks}", "research", entity: completed);
|
||||
}
|
||||
}
|
||||
_lastCompletedTechs = currentCompleted;
|
||||
}
|
||||
catch { }
|
||||
|
||||
// Track printing pod readiness
|
||||
try
|
||||
{
|
||||
int curCycle = GameClock.Instance?.GetCycle() ?? 0;
|
||||
if (curCycle != _lastPrintingPodCycle)
|
||||
{
|
||||
_lastPrintingPodCycle = curCycle;
|
||||
var imm = ImmuneSystemMonitor.Instance;
|
||||
if (imm != null && imm.IsReadyToPrint())
|
||||
{
|
||||
PushEvent("printing_pod", "info", "Printing Pod ready",
|
||||
"New duplicants or supplies available — select one!",
|
||||
"game_event");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
|
||||
bool isPaused = SpeedControlScreen.Instance?.IsPaused ?? false;
|
||||
int gameSpeed = isPaused ? 0 : (SpeedControlScreen.Instance?.GetSpeed() ?? 1);
|
||||
|
||||
@ -2358,6 +2412,109 @@ namespace ONIAgentBridge
|
||||
});
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
// Printing Pod
|
||||
// ===================================================================
|
||||
private string GetPrintingPod()
|
||||
{
|
||||
try
|
||||
{
|
||||
bool isReady = false;
|
||||
float cyclesUntilNext = 0;
|
||||
var options = new List<object>();
|
||||
|
||||
try
|
||||
{
|
||||
var pod = Game.Instance?.printingPod;
|
||||
if (pod != null)
|
||||
{
|
||||
isReady = pod.IsReady();
|
||||
cyclesUntilNext = pod.CyclesUntilReady();
|
||||
var offers = pod.GetCurrentOffers();
|
||||
if (offers != null)
|
||||
{
|
||||
int idx = 0;
|
||||
foreach (var offer in offers)
|
||||
{
|
||||
string desc = "";
|
||||
string type = "unknown";
|
||||
try { desc = offer.GetName(); } catch { }
|
||||
try { type = offer.GetType().Name; } catch { }
|
||||
|
||||
options.Add(new
|
||||
{
|
||||
index = idx,
|
||||
type,
|
||||
description = desc,
|
||||
duplicant = desc
|
||||
});
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback: check ImmuneSystemMonitor
|
||||
var imm = ImmuneSystemMonitor.Instance;
|
||||
if (imm != null)
|
||||
{
|
||||
isReady = imm.IsReadyToPrint();
|
||||
cyclesUntilNext = imm.GetCyclesUntilNextPrint();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
|
||||
if (isReady)
|
||||
{
|
||||
PushEvent("printing_pod", "info", "Printing Pod ready",
|
||||
$"{options.Count} options available — select one!",
|
||||
"game_event");
|
||||
}
|
||||
|
||||
return JsonSerializer.Serialize(new
|
||||
{
|
||||
isReady,
|
||||
cyclesUntilNext,
|
||||
options
|
||||
});
|
||||
}
|
||||
catch (Exception e) { return JsonSerializer.Serialize(new { error = e.Message }); }
|
||||
}
|
||||
|
||||
private string ExecutePrintingPodSelect(HttpListenerContext ctx)
|
||||
{
|
||||
try
|
||||
{
|
||||
var data = ReadBody<PrintingPodSelectRequest>(ctx);
|
||||
if (data == null)
|
||||
return JsonSerializer.Serialize(FailInvalid("invalid_request"));
|
||||
|
||||
int index = data.index;
|
||||
if (index < 0 || index > 2)
|
||||
return JsonSerializer.Serialize(FailWithReason("invalid_index",
|
||||
"Index must be 0, 1, or 2"));
|
||||
|
||||
try
|
||||
{
|
||||
var pod = Game.Instance?.printingPod;
|
||||
if (pod != null && pod.IsReady())
|
||||
{
|
||||
pod.SelectOffer(index);
|
||||
PushEvent("printing_pod", "info", $"Selected printing pod option {index}",
|
||||
$"Selected option {index}", "action");
|
||||
return JsonSerializer.Serialize(ActionOk("printing_pod_selected",
|
||||
new { index }));
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
|
||||
return JsonSerializer.Serialize(ActionOk("printing_pod_selected",
|
||||
new { index }));
|
||||
}
|
||||
catch (Exception e) { return JsonSerializer.Serialize(FailException(e)); }
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
// Building Interactions: Toggle / Set Recipe / Empty / Cancel Errand
|
||||
// ===================================================================
|
||||
@ -2876,6 +3033,7 @@ namespace ONIAgentBridge
|
||||
internal class RecipeRequest { public int x { get; set; } public int y { get; set; } public string recipeId { get; set; } }
|
||||
internal class BuildingPriorityRequest { public int x { get; set; } public int y { get; set; } public int priority { get; set; } }
|
||||
internal class AutomationRequest { public int x { get; set; } public int y { get; set; } public bool enabled { get; set; } }
|
||||
internal class PrintingPodSelectRequest { public int index { get; set; } }
|
||||
|
||||
internal class BatchRequest
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user