Skip to content

Instantly share code, notes, and snippets.

@santolucito
Created July 24, 2021 20:43
Show Gist options
  • Save santolucito/a01927be45a2a7a8e02ce9a50ddd8e75 to your computer and use it in GitHub Desktop.
Save santolucito/a01927be45a2a7a8e02ce9a50ddd8e75 to your computer and use it in GitHub Desktop.
Age of Empires 2 TC Idle time calculator
import os
from typing import ItemsView
from construct.core import Switch
from mgz import header, body
from pprint import pprint
tc_building_id = 0
total_time_until_castle_click = 0
tc_work_time = 0
VILL_ID = 83 #ids pulled from test runs and manual inspection of parsed save file TODO is there a better way?
VILL_TIME = 25000 #in ms, pulled from aoe2 wiki
LOOM_ID = 22
LOOM_TIME = 25000
WHEELBARROW_ID = 213
WHEELBARROW_TIME = 75000
TOWNWATCH_ID = 8
TOWNWATCH_TIME = 25000
FEUDAL_ID = 101
FEUDAL_TIME = 130000
CASTLE_ID = 102
ID_INFO = dict([(VILL_ID, VILL_TIME),
(LOOM_ID, LOOM_TIME),
(WHEELBARROW_ID, WHEELBARROW_TIME),
(TOWNWATCH_ID, TOWNWATCH_TIME),
(FEUDAL_ID, FEUDAL_TIME)
])
def isVillQueue(action):
global tc_building_id
if action.type == "de_queue" and action.unit_type == VILL_ID:
tc_building_id = action.building_ids[0]
return True
else:
return False
#TODO how to distinguish what we are dequeing (vill vs tech).
# right now we assume dequeuing is always dequeueing a vill (which also works for loom and townwatch b/c they take the same time)
# TODO how could we know which item in the queue we are dequeuing (e.g. currently being completed, or not yet started)
# this info is not relevant to "effective" idle time calculations, but necessary for overall idle time
def isVillDequeue(action):
return action.type == "order" and action.building_id == -1 and tc_building_id in action.unit_ids
def isTCResearch(action):
return action.type == "research" and action.building_id == tc_building_id
def tcResearchTime(action):
return ID_INFO[action.technology_type]
# to calculate tc work time, add up the build time for everything queued, and substract the build time of everything dequeued
# this assumes dequeuing a partially completed items (vills or research) should count as idle time
# e.g. queue a vill for 15 seconds, then cancelling that vill before it is completed means your tc was effectively idle for 15 seconds.
def inducedTCWorkTime(action):
tc_work_time = 0
if hasattr(action, "player_id") and action.player_id == 2: #ignore player 2 actions
return tc_work_time
if isVillQueue(action):
print("ENQUEUE")
print(op)
tc_work_time += action.queue_amount * VILL_TIME
elif isVillDequeue(action):
print("DEQUEUE")
print(op)
tc_work_time -= VILL_TIME #can only dequeue one unit at a time?
elif isTCResearch(action):
tc_work_time += tcResearchTime(action)
return tc_work_time
def isCastleResearch(op):
return op.type == "action" and op.action.type == "research" and op.action.technology_type == CASTLE_ID
#TODO how to make this work cross-platform
with open('/mnt/c/Users/mark-omi/Games/Age of Empires 2 DE/76561198177849325/savegame/rec.aoe2record', 'rb') as data:
eof = os.fstat(data.fileno()).st_size
info = header.parse_stream(data)
#print(info)
body.meta.parse_stream(data)
#exit()
tc_work_time = 0
while data.tell() < eof:
op = body.operation.parse_stream(data)
if isCastleResearch(op):
#TODO would like to only break when castle research actually starts (rather than just being queued)
#to do this, we need to keep track of when each item queued actually completes
#we are essentially rebuilding a tiny portion of the game engine by doing this
break
elif (op.type == "action" and not op.action.type_int in [103, 130]): #103, 130 are unknown commands
#print(op.action)
tc_work_time += inducedTCWorkTime(op.action)
elif (op.type == "sync"):
total_time_until_castle_click += op.time_increment
print(tc_work_time)
print(total_time_until_castle_click)
print("pre-castle idle time: " + str((total_time_until_castle_click - tc_work_time)/1000))
@yukozh
Copy link

yukozh commented Jun 22, 2024

why line 52 is intercepting "order" action? not "stop"?

@santolucito
Copy link
Author

At the time, I believe "order" was the way the commamds encoded that user ordered an action. The rest of that conditional is what specified a dequeue order specifically. The API might have changed since I wrote this though

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment