# MMORPG Skill Rotation Optimizer (Rogue Class)
# Optimizes skill rotation to maximize total damage output
# Subject to energy/combo point resource constraints and ability cooldowns
# Demonstrates temporal sequencing with time-indexed variables and resource tracking
model max_damage
set S = getSkills() # Available skills
set T = getTime() # Time steps in rotation
set F = {0} # Singleton set for constant indexing
set G = {0, 1} # Energy level options
# Continuous variables for resource tracking
var 0 <= e <= 100, forall t in T # Energy at time t
var 0 <= k <= 5, forall t in T # Combo points at time t
var 0 <= pi <= 100, forall t in T # Energy spent at time t
var 0 <= gamma <= 5, forall t in T # Combo points spent at time t
# Skill properties loaded from CSV
const d = getDamage(), forall s in S # Base damage of skill s
const epsilon = getEnergy(), forall s in S # Energy cost/gain of skill s (negative = cost)
const delta = {100, 150}, forall g in G # Starting energy levels
const kappa = getComboPoints(), forall s in S # Combo points generated/consumed by skill s
const tau = getCooldowns(), forall s in S # Cooldown duration of skill s
const rho = {0.2}, forall f in F # Energy regeneration rate
const r = {10}, forall f in F # Energy regeneration per tick
const M = {323}, forall f in F # Big-M constant for logical constraints
const psi = getFinishingMove(), forall s in S # 1 if skill is finishing move, 0 otherwise
# Decision variables
bin x forall t in T, forall s in S # 1 if skill s is used at time t
bin y, forall t in T, forall s in S # Auxiliary variable for constraints
# Objective: Maximize total damage over the rotation
max sum_{s in S}{sum_{t in T}{x_{t,s} * d_{s}}}
# Energy balance constraint (for t > 0)
# Energy at time t = energy at t-1 + energy from skills - energy spent + regeneration
constr e_{t} = e_{t-1} + sum_{s in S}{epsilon_{s} * x_{t,s}} - pi_{t} + r_{0} * rho_{0},
forall t in T setminus {0}
# Combo point balance constraint (for t > 0)
# Combo points at time t = combo points at t-1 + points generated - points consumed
constr k_{t} = k_{t-1} + sum_{s in S}{kappa_{s} * x_{t,s}} - gamma_{t},
forall t in T setminus {0}
# Exactly one skill must be used at each time step
constr sum_{s in S}{x_{t,s}} = 1, forall t in T
# Finishing move constraint: If finishing move is used, must have required combo points
# This ensures that when a finishing move s is selected at time t, the combo points k_{t}
# must be at least equal to the combo points consumed by that skill
constr -x_{t,s} * kappa_{s} * psi_{s} + M_{0} - M_{0} * x_{t,s} >= k_{t} * psi_{s},
forall s in S, forall t in T
# Initial combo points constraint (at t=0)
# Set initial combo points based on skills used at start
constr k_{t} = sum_{s in S}{kappa_{s} * x_{t,s}},
forall t in T bigcap {0}
# Initial energy constraint (at t=0)
# Set starting energy level plus energy from first skill
constr e_{t} = delta_{0} + sum_{s in S}{epsilon_{s} * x_{t,s}},
forall t in T bigcap {0}
end
import elytica
import pandas
import json
# Global variables for model data
data = None
steps = None
def h(t, T):
"""Helper function to generate time range from t to T"""
return json.dumps([i for i in range(int(t), int(T))])
def getSkills():
"""Return list of skill IDs (indices into data frame)"""
return json.dumps([i for i in range(0, len(data["Name"].to_list()))])
def getDamage():
"""Return base damage values for all skills"""
return json.dumps(data["BaseDamage"].to_list())
def getEnergy():
"""Return energy cost/gain for all skills (negative values = cost)"""
return json.dumps(data["Energy"].to_list())
def getComboPoints():
"""Return combo points generated/consumed by each skill (negative = consumed)"""
return json.dumps(data["ComboPoints"].to_list())
def getCooldowns():
"""Return cooldown duration for each skill"""
return json.dumps(data["Cooldown"].to_list())
def getFinishingMove():
"""Return indicator: 1 if skill is a finishing move, 0 otherwise"""
return json.dumps(data["FinishingMove"].to_list())
def getTime():
"""Return list of time steps in the rotation"""
return json.dumps([i for i in range(0, steps)])
def main():
global data, steps
# Configuration
steps = 5 # Number of time steps to optimize
# Load skill data from CSV
data = pandas.read_csv(
"rogue.csv",
usecols=["Name", "BaseDamage", "ComboPoints", "Energy", "Cooldown",
"FinishingMove", "Visible", "IncreasedDamage", "DurationIncreasedDamage"]
)
# Initialize and solve the optimization model
elytica.init_model("max_damage")
elytica.run_model("max_damage")
print("\n" + "="*70)
print("MMORPG SKILL ROTATION OPTIMIZATION SOLUTION")
print("="*70)
print("\nOptimized Rotation:")
print("-"*70)
# Collect and display the optimal rotation
rotation = []
for t in range(0, steps):
energy = elytica.get_variable_value("max_damage", f"e{t}")
combopoints = elytica.get_variable_value("max_damage", f"k{t}")
for s in range(0, len(data["Name"].to_list())):
val = elytica.get_variable_value("max_damage", f"x{t},{s}")
if val > 0:
skill_name = data["Name"][s]
damage = data["BaseDamage"][s]
time_sec = t * 0.2 # Convert time step to seconds (assuming 0.2s GCD)
rotation.append({
"time_step": t,
"time_sec": time_sec,
"skill": skill_name,
"damage": damage,
"energy": energy,
"combo_points": combopoints
})
print(f" t={t} ({time_sec:.1f}s): {skill_name:20s} | "
f"Damage={damage:5.0f} | Energy={energy:5.1f} | CP={combopoints:.0f}")
# Get objective value (total damage)
total_damage = elytica.get_best_primal_bound("max_damage")
print("-"*70)
print(f"Total Damage: {total_damage:.2f}")
print("="*70)
# Prepare results for output
results = {
"objective_value": total_damage,
"rotation": rotation,
"time_steps": steps
}
elytica.write_results(json.dumps(results, indent=2))
return 0
# Expected CSV file format (rogue.csv):
#
# Name,BaseDamage,ComboPoints,Energy,Cooldown,FinishingMove,Visible,IncreasedDamage,DurationIncreasedDamage
# Eviscerate1,495,-1,-35,0,1,1,0,0
# Eviscerate2,990,-2,-35,0,1,1,0,0
# Eviscerate3,1485,-3,-35,0,1,1,0,0
# Eviscerate4,1980,-4,-35,0,1,1,0,0
# Eviscerate5,2475,-5,-35,0,1,1,0,0
# MarkedForDeath,0,5,0,30,0,1,0,0
# Backstab,956,1,-35,0,0,1,0,0
# AutoAttack,7,0,0,1.8,0,1,0,0
# Idle,0,0,0,0,0,1,0,0
# SymbolsOfDeath,0,0,40,30,0,1,0.15,10
#
# Field descriptions:
# - Name: Skill name
# - BaseDamage: Base damage dealt by the skill
# - ComboPoints: Combo points generated (positive) or consumed (negative)
# - Energy: Energy cost (negative) or gain (positive)
# - Cooldown: Cooldown duration in seconds
# - FinishingMove: 1 if finishing move (requires combo points), 0 otherwise
# - Visible: 1 if skill should be displayed in UI
# - IncreasedDamage: Damage multiplier buff applied by skill
# - DurationIncreasedDamage: Duration of damage buff in seconds
Read this through MCP: read with path: "examples/mmorpg-rotation", or resource hlpl://examples/mmorpg-rotation.