@zerospacegg/anrubic
Version:
Anrubic - ZeroSpace.gg MCP Server for AI agents to access game data
344 lines (331 loc) • 11.6 kB
JavaScript
import createDebug from "debug";
// @ts-ignore - JavaScript module without TypeScript declarations
import { PklDynamicEvaluator } from "../../scripts/pkl-utils/dynamic-evaluator.js";
const debugPkl = createDebug("anrubic:pkl");
let pklEvaluator = null;
let pklAvailable = false;
let initializationAttempted = false;
/**
* Initialize Pkl evaluator with configurable command
* @param pklCommand - Pkl command to use (default: 'pkl')
* @returns Initialization result
*/
export async function initializePkl(pklCommand = "pkl") {
if (initializationAttempted && pklEvaluator) {
return { success: true, pklAvailable };
}
initializationAttempted = true;
try {
pklEvaluator = new PklDynamicEvaluator({
tempDir: "/tmp/pkl-eval-anrubic",
cacheEnabled: true,
pklCommand,
});
// Test if Pkl is available with a simple evaluation
const testTemplate = `result = "pkl-available"`;
const testResult = await pklEvaluator.evaluateTemplate(testTemplate);
if (testResult?.result === "pkl-available") {
pklAvailable = true;
debugPkl("Pkl integration ONLINE! Advanced combat analysis enabled!");
return {
success: true,
pklAvailable: true,
version: "detected",
};
}
else {
throw new Error("Pkl test evaluation failed");
}
}
catch (error) {
pklAvailable = false;
debugPkl("Pkl not available - falling back to JSON mode for basic analysis");
return {
success: false,
pklAvailable: false,
error: error instanceof Error ? error.message : String(error),
};
}
}
/**
* Check if Pkl is available for advanced analysis
*/
export function isPklAvailable() {
return pklAvailable;
}
/**
* Get the Pkl evaluator instance (may be null if not available)
*/
export function getPklEvaluator() {
return pklEvaluator;
}
/**
* Execute combat analysis using Pkl API files
* @param unitA - First unit identifier
* @param unitB - Second unit identifier
* @returns Combat analysis result
*/
export async function executeCombatAnalysis(unitA, unitB) {
if (!pklEvaluator || !pklAvailable) {
throw new Error("Pkl not available - cannot perform advanced combat analysis");
}
const template = `
import "iolin/api/unit-combat-meta.pkl" as CombatMeta
// Execute the combat analysis
result = CombatMeta.result
metadata = new {
analysis = "pkl-powered-combat"
version = "anrubic-v1"
units = List("${escapeString(unitA)}", "${escapeString(unitB)}")
}
`;
try {
const result = await pklEvaluator.evaluateTemplate(template, {
unitAId: unitA,
unitBId: unitB,
}, {
timeout: 15000,
format: "json",
});
return {
success: true,
data: result,
source: "pkl-api",
units: [unitA, unitB],
};
}
catch (error) {
throw new Error(`Combat analysis failed: ${error instanceof Error ? error.message : String(error)}`);
}
}
/**
* Execute unit comparison using Pkl API
* @param units - Array of unit identifiers
* @returns Unit comparison result
*/
export async function executeUnitComparison(units) {
if (!pklEvaluator || !pklAvailable) {
throw new Error("Pkl not available - cannot perform advanced unit comparison");
}
const unitsListStr = units.map((u) => `"${escapeString(u)}"`).join("; ");
const template = `
import "iolin/api/unit-comparison.pkl" as UnitComparison
// Override the module's parameters by creating a new context
result = (UnitComparison) {
units = new Listing { ${unitsListStr} }
analysis = "full"
includeTiers = true
includeUpgrades = false
}.result
metadata = new {
analysis = "pkl-powered-comparison"
version = "anrubic-v1"
searchedUnits = new Listing { ${unitsListStr} }
}
`;
try {
const result = await pklEvaluator.evaluateTemplate(template, {}, {
timeout: 20000,
format: "json",
});
return {
success: true,
data: result,
source: "pkl-api",
units,
};
}
catch (error) {
throw new Error(`Unit comparison failed: ${error instanceof Error ? error.message : String(error)}`);
}
}
/**
* Find counters for a specific unit using custom Pkl analysis
* @param targetUnit - Unit to find counters for
* @param limit - Maximum number of counters to return
* @returns Counter analysis result
*/
export async function findCounters(targetUnit, limit = 10) {
if (!pklEvaluator || !pklAvailable) {
throw new Error("Pkl not available - cannot perform counter analysis");
}
const template = `
import "iolin/meta/all.pkl" as AllData
// Hide intermediate variables to reduce output size
local targetUnitName = "${escapeString(targetUnit)}"
// Find the target unit
local function findUnit(identifier: String) = AllData.all
.filter((entity) -> entity.type == "unit")
.find((unit) ->
unit.name.toLowerCase() == identifier.toLowerCase() ||
(unit.slug != null && unit.slug.toLowerCase().contains(identifier.toLowerCase()))
)
local targetUnit = findUnit(targetUnitName)
// Get all combat units for analysis
local allUnits = AllData.all
.filter((entity) -> entity.type == "unit")
.filter((unit) -> unit.name != targetUnitName)
// Calculate who wins against target unit
local function calculateWinners(target) =
allUnits
.map((challenger) ->
let (targetAttack = target.ability?.keys?.map((key) -> target.ability[key])?.filter((a) -> a.subtype == "attack")?.firstOrNull)
let (challengerAttack = challenger.ability?.keys?.map((key) -> challenger.ability[key])?.filter((a) -> a.subtype == "attack")?.firstOrNull)
let (targetDamage = targetAttack?.damage ?? 0)
let (challengerDamage = challengerAttack?.damage ?? 0)
let (targetArmor = target.armor ?? 0)
let (challengerArmor = challenger.armor ?? 0)
let (targetArmorReduction = targetArmor / (targetArmor + 6))
let (challengerArmorReduction = challengerArmor / (challengerArmor + 6))
let (effectiveDamageToTarget = challengerDamage * (1 - targetArmorReduction))
let (effectiveDamageToChallenger = targetDamage * (1 - challengerArmorReduction))
let (shotsToKillTarget = if (effectiveDamageToTarget > 0) (target.hp / effectiveDamageToTarget).ceil else 999)
let (shotsToKillChallenger = if (effectiveDamageToChallenger > 0) (challenger.hp / effectiveDamageToChallenger).ceil else 999)
let (timeToKillTarget = shotsToKillTarget * (challengerAttack?.cooldown ?? 1))
let (timeToKillChallenger = shotsToKillChallenger * (targetAttack?.cooldown ?? 1))
let (challengerWins = timeToKillTarget < timeToKillChallenger)
let (advantage = if (challengerWins) (timeToKillChallenger - timeToKillTarget) else 0)
new Dynamic {
name = challenger.name
faction = challenger.id?.split("/")[1] ?? "unknown"
tier = challenger.tier ?? "unknown"
wins = challengerWins
timeAdvantage = advantage
costEfficiency = if ((challenger.hexiteCost ?? 0) > 0 && (target.hexiteCost ?? 0) > 0)
((target.hexiteCost ?? 0) / (challenger.hexiteCost ?? 0)) else 0
damageDealt = effectiveDamageToTarget.floor
shotsNeeded = shotsToKillTarget
timeToKill = timeToKillTarget
}
)
.filter((result) -> result.wins)
.sortWith((a, b) -> if (b.timeAdvantage > a.timeAdvantage) 1 else if (b.timeAdvantage < a.timeAdvantage) -1 else 0)
.take(${limit})
result = if (targetUnit != null)
new Dynamic {
target = new Dynamic {
name = targetUnit.name
faction = targetUnit.id?.split("/")[1] ?? "unknown"
tier = targetUnit.tier ?? "unknown"
health = targetUnit.hp ?? 0
armor = targetUnit.armor ?? 0
cost = targetUnit.hexiteCost ?? 0
}
counters = calculateWinners(targetUnit)
totalCountersFound = calculateWinners(targetUnit).length
analysis = "Strong counters based on time-to-kill advantage"
success = true
}
else
new Dynamic {
error = "Target unit not found: " + targetUnitName
suggestions = allUnits.take(5).map((u) -> u.name)
success = false
}
metadata = new Dynamic {
analysis = "pkl-powered-counters"
version = "anrubic-v1"
targetUnit = targetUnitName
}
`;
try {
const result = await pklEvaluator.evaluateTemplate(template, {}, {
timeout: 20000,
format: "json",
});
return {
success: true,
data: result,
source: "pkl-counter-analysis",
targetUnit,
};
}
catch (error) {
throw new Error(`Counter analysis failed: ${error instanceof Error ? error.message : String(error)}`);
}
}
/**
* Execute Emperor sacrifice calculation using Pkl API
* @param thralls - Number of Thralls to sacrifice
* @param steelsworn - Number of Steelsworn to sacrifice
* @param darkDisciples - Number of Dark Disciples to sacrifice
* @returns Emperor calculation result
*/
export async function executeEmperorCalculation(thralls, steelsworn, darkDisciples) {
if (!pklEvaluator || !pklAvailable) {
throw new Error("Pkl not available - cannot perform Emperor calculation");
}
const template = `
import "iolin/api/emperor-calculator.pkl" as EmperorCalc
// Override the module's parameters by creating a new context
result = (EmperorCalc) {
thrallCount = ${thralls}
steelswornCount = ${steelsworn}
darkDiscipleCount = ${darkDisciples}
}.result
metadata = new {
analysis = "pkl-powered-emperor-calculator"
version = "anrubic-v1"
sacrifices = new {
thralls = ${thralls}
steelsworn = ${steelsworn}
darkDisciples = ${darkDisciples}
}
}
`;
try {
const result = await pklEvaluator.evaluateTemplate(template, {}, {
timeout: 15000,
format: "json",
});
return {
success: true,
data: result,
source: "pkl-emperor-calculator",
sacrifices: { thralls, steelsworn, darkDisciples },
};
}
catch (error) {
throw new Error(`Emperor calculation failed: ${error instanceof Error ? error.message : String(error)}`);
}
}
/**
* Get health status of Pkl integration
*/
export async function getPklHealth() {
if (!pklEvaluator) {
return {
available: false,
status: "not-initialized",
message: "Pkl evaluator not initialized",
};
}
try {
// Simple test evaluation to verify PKL is working
const testTemplate = `
result = "pkl-health-ok"
version = "test"
`;
const testResult = await pklEvaluator.evaluateTemplate(testTemplate, {}, { timeout: 5000 });
return {
available: pklAvailable,
status: "healthy",
message: "Pkl evaluator is working correctly",
testResult: testResult.result,
};
}
catch (error) {
return {
available: false,
status: "error",
error: error instanceof Error ? error.message : String(error),
};
}
}
/**
* Escape string for safe Pkl template insertion
*/
function escapeString(str) {
return str.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
}
//# sourceMappingURL=pkl-evaluator.js.map