Skip to main content

aoe2-genie-tooling

High-level Python toolkit for editing Age of Empires II Definitive Edition DAT files.

An object-oriented API with proper wrappers that handle multi-civ propagation automatically.

Installation

pip install aoe2-genie-tooling

Documentation

Full documentation is available in the docs/ directory or at https://gokumodder.github.io/aoe2-genie-tooling/.

Why This Library?

  • Proper Objects – Units, Graphics, Sounds return handle objects with typed properties
  • Multi-Civ Propagation – Changes automatically apply across all civilizations
  • Attribute Flattening – Access deeply nested properties directly (unit.move_sound instead of unit.bird.move_sound)
  • Type-Safe Datasets – IntEnum constants for resources, tasks, attack classes
  • Fluent Builders - Easy-to-use builders for tasks (unit.add_task.combat()) and effects

Quick Start

from aoe2_genie_tooling import GenieWorkspace

# Load workspace
workspace = GenieWorkspace.load("empires2_x2_p1.dat")

# Get managers
unit_manager = workspace.unit_manager
graphic_manager = workspace.graphic_manager
sound_manager = workspace.sound_manager
tech_manager = workspace.tech_manager
civ_manager = workspace.civ_manager

# Create a custom unit
unit = unit_manager.create("Elite Guard", base_unit_id=4)  # Clone from Archer
unit.hit_points = 120
unit.max_range = 6.0

# Save changes
workspace.save("output.dat")

Units: Create, Clone, Move, Delete

Creating Units

unit_manager = workspace.unit_manager

# Create new unit (clones from base_unit_id)
unit = unit_manager.create(
    name="My Unit",
    base_unit_id=4,               # Clone from Archer
    unit_id=1500,                 # Optional: specific ID (auto-assigns if None)
    enable_for_civs=[0, 1, 2],    # Optional: specific civs (all if None)
)

Cloning Units

unit_manager = workspace.unit_manager

# Clone existing unit to a new ID
clone = unit_manager.clone_into(
    src_unit_id=4,       # Source: Archer
    dst_unit_id=2000,    # Destination ID
    name="Archer Clone",
    on_conflict="overwrite",  # "error" | "overwrite"
)

Moving Units

unit_manager = workspace.unit_manager

# Move unit to different ID (leaves placeholder at source)
unit_manager.move(
    src_unit_id=2000,
    dst_unit_id=2500,
    on_conflict="swap",  # "error" | "overwrite" | "swap"
)

Getting Existing Units

unit_manager = workspace.unit_manager

# Get handle for existing unit
archer = unit_manager.get(4)
print(archer.name)       # "Archer"
print(archer.hit_points) # 30

Attacks & Armors

unit_manager = workspace.unit_manager
unit = unit_manager.get(4)  # Archer

# Read current attacks
for attack in unit.attacks:
    print(f"Class {attack.class_}: {attack.amount} damage")

# Add new attack (pierce damage)
unit.add_attack(class_=3, amount=6)

# Add armor (melee armor)
unit.add_armour(class_=4, amount=2)

Tasks (Unit Commands)

from Datasets import Task, Resource

unit_manager = workspace.unit_manager
unit = unit_manager.get(83)  # Villager

# List all tasks
for task in unit.tasks.list_tasks():
    print(f"Task {task.id}: type={task.task_type}")

# Add a gather task using builder
unit.add_task.gather(
    resource_in=Resource.FOOD,
    resource_out=Resource.FOOD,
    work_range=0.5,
)

# Remove a task by ID
unit.remove_task(0)

# Clear all tasks
unit.clear_tasks()

Graphics: Create and Assign

graphic_manager = workspace.graphic_manager
unit_manager = workspace.unit_manager

# Create a new graphic
graphic = graphic_manager.add_graphic(
    file_name="my_unit_attack.slp",
    frame_count=15,
    angle_count=8,
)

print(f"Created graphic ID: {graphic.id}")

# Assign to unit
unit = unit_manager.create("Slinger", base_unit_id=4)
unit.standing_graphic_1 = graphic.id
unit.attack_graphic = graphic.id

Sounds: Create and Assign

sound_manager = workspace.sound_manager
unit_manager = workspace.unit_manager

# Create a new sound
sound = sound_manager.add_new("Custom Attack")
sound.new_sound(filename="custom_attack.wav", probability=100)

print(f"Created sound ID: {sound.id}")

# Assign to unit
unit = unit_manager.get(4)
unit.attack_sound = sound.id
unit.move_sound = sound.id

Complete Example: Custom Unit with Assets

from aoe2_genie_tooling import GenieWorkspace
from Datasets import Resource, UnitClass

workspace = GenieWorkspace.load("empires2_x2_p1.dat")

# Get managers
unit_manager = workspace.unit_manager
graphic_manager = workspace.graphic_manager
sound_manager = workspace.sound_manager

# 1. Create custom graphics
idle_gfx = graphic_manager.add_graphic("hero_idle.slp", frame_count=10, angle_count=8)
attack_gfx = graphic_manager.add_graphic("hero_attack.slp", frame_count=15, angle_count=8)

# 2. Create custom sound
attack_sound = sound_manager.add_new("hero_attack")
attack_sound.new_sound("hero_attack.wav")

# 3. Create the unit (clone from Knight)
hero = unit_manager.create("Hero Knight", base_unit_id=38)

# 4. Set basic stats
hero.hit_points = 200
hero.speed = 1.5
hero.class_ = UnitClass.CAVALRY

# 5. Assign graphics
hero.standing_graphic_1 = idle_gfx.id
hero.attack_graphic = attack_gfx.id

# 6. Assign sound
hero.attack_sound = attack_sound.id

# 7. Set attacks and armor
hero.set_attack(class_=4, amount=15)   # Melee
hero.set_attack(class_=11, amount=10)  # Bonus vs Buildings
hero.add_armour(class_=3, amount=5)    # Pierce armor

# 8. Set cost
hero.cost.food = 100
hero.cost.gold = 80

# 9. Set resource storage (drops gold on death)
hero.resource_1(type=Resource.GOLD, amount=50.0, flag=2)

# 10. Save
workspace.save("output.dat")

print(f"Created Hero Knight at ID {hero.id}")

Datasets (Constants)

from Datasets import (
    Resource,      # FOOD, WOOD, GOLD, STONE, etc.
    UnitClass,     # INFANTRY, ARCHER, CAVALRY, etc.
    Attribute,     # Object attributes for effects
    Task,          # GATHER, BUILD, ATTACK, etc.
    StoreMode,     # Resource storage flags
)

unit.class_ = UnitClass.ARCHER

Exceptions

Exception When Raised
UnitIdConflictError Target ID exists with on_conflict="error"
GapNotAllowedError Would create gaps with fill_gaps="error"
InvalidIdError Negative or non-existent ID
TemplateNotFoundError No template unit for cloning
ValidationError Workspace validation failed

Requirements

  • Python 3.11+
  • GenieDatParser (local dependency - Rust-backed DAT parser)

License

LGPL-3.0

Author

GoKuModder

Metadata

Release files for aoe2-genie-tooling 1.2.4

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for aoe2-genie-tooling 1.2.4
File Size Uploaded
aoe2_genie_tooling-1.2.4.tar.gz 161.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for aoe2-genie-tooling 1.2.4
File Interpreter ABI Platform
aoe2_genie_tooling-1.2.4-py3-none-any.whl Python 3 none any Details

Total release size: 412.7 kB

Release files / aoe2_genie_tooling-1.2.4.tar.gz

Download URL aoe2_genie_tooling-1.2.4.tar.gz
Size 161.2 kB
Tags Source
SHA-256 checksum
How to use checksums
3071fe40bfbf1ee8b765b62b77bb3614f0f318a9499cf3b807a067630157f89a
BLAKE2b-256 checksum
How to use checksums
0796a9a577bf7ba6cdcc99491cec5deced912f42b0105dafca7be293dfd3e4a7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Feb 3, 2026.

Transparency log

Release files / aoe2_genie_tooling-1.2.4-py3-none-any.whl

Download URL aoe2_genie_tooling-1.2.4-py3-none-any.whl
Size 251.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
caf0f6e9dfe265f2e0af06815322360f1208d9f1aaf79fd93d5cd21083c006cd
BLAKE2b-256 checksum
How to use checksums
b0e0e981eed081de880b2a5d5426caeb136a63eb5db399d777c0217d40fa00a3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Feb 3, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

1.2.4 This release

2 release files

1.2.2

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page