Skip to main content

High-level Python toolkit for editing AoE2 DE DAT files

Project description

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

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

aoe2_genie_tooling-1.2.2.tar.gz (197.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

aoe2_genie_tooling-1.2.2-py3-none-any.whl (230.8 kB view details)

Uploaded Python 3

File details

Details for the file aoe2_genie_tooling-1.2.2.tar.gz.

File metadata

  • Download URL: aoe2_genie_tooling-1.2.2.tar.gz
  • Upload date:
  • Size: 197.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.0

File hashes

Hashes for aoe2_genie_tooling-1.2.2.tar.gz
Algorithm Hash digest
SHA256 0784e669b3c503b3d36eee74104d166b7010bd798d82ec6499cb45142f8c2e3d
MD5 05d74d39aa54436e2339497507253c68
BLAKE2b-256 1ffa46731eaf2d250dfd3751f661fd1bf9aab62e4b49eea7771fa9eb8bc17e5c

See more details on using hashes here.

File details

Details for the file aoe2_genie_tooling-1.2.2-py3-none-any.whl.

File metadata

File hashes

Hashes for aoe2_genie_tooling-1.2.2-py3-none-any.whl
Algorithm Hash digest
SHA256 09ad3a875bbea2fa8377b8ed04095c405a5a942428fd5c3de827cb6c1b5a024a
MD5 a25cc969bd51413227d2f0a28d8e5620
BLAKE2b-256 1942dbd4aea0a4667cf149a55e58d89193d69c9fdcc9155c93dbb08c98501145

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page