Skip to main content

Library to help create City of Heroes binds as simple or advanced as you'd like.

Project description

A Python library for creating advanced key binds and macros for City of Heroes.

Main Features

  • Bind, Macro, and CommandGroup classes for structured creation of their in-game counterparts

  • BindFile class to manage collections of binds and more as well as file publishing

  • Template based classes for the above to assist in content generation

  • Common rotating bind classes for easy setup of complex bind file structures

Advanced Features

  • BindFileGraph class for custom bind file networks

  • BGFPublisher for publishing complex bind file graphs

Installation

Install from PyPI using pip:

pip install CityOfBinds

Background

City of Heroes uses a powerful slash command system for most of its in-game operations. Things like targeting an enemy, activating a power, or even just moving can all be executed by their slash command counterparts.

While slash commands are typically ran in the chat window, they can also be bound to physical keys and in-game buttons. Respectively these would be known as binds (aka keybinds) and macros.

One of the slash commands available to users is the /bindloadfile command. This command allows users to load a file (called a bind file) containing multiple binds and macros all at once. Upon loading, these new binds would be immediately active in the game.

As more than one command can be bound to a key or button, these binds and macros can perform an action, and at the same time load a bind file to rebind that very same key or button! This is what is known as a rotating bind.

You can do some very cool things with rotating binds, whether it be for play optimization (e.g., set multiple powers on auto cast just by moving around) or roleplay (think casting a power and yelling a rotating catch phrase)!

No matter what you can think of yourself, this library can help you create the binds and files needed to bring your ideas to life in City of Heroes.

Additional Resources

Walkthrough

Basic Game Components

The basic building blocks of this library are: Bind, Macro, and CommandGroup.

We can initialize a Bind by specifyin a trigger (aka the key on your keyboard) and optionally the commands we’d like to exectute when that trigger is pressed. For now let’s just create a blank bind bound to the H key.

from CityOfBinds import Bind

# Create a bind on the H key
h_bind = Bind(key="H")

Now we can call one of the Bind’s methods to add commands. Let’s make it so hasten is activated when we press H.

# Add the hasten power command
h_bind.commands.add_power("hasten")

Bind supports many different types of methods including toggling on, off, and location based execution of powers. For a full list of methods see the Bind class documentation.

If we were to print the string representation of this bind, we’d get the formatted bind as it’d be used in a bind file, and via the game_str method, we can even get the exact command we’d use in-game to execute this bind.

print(h_bind.get_str())
# H "powexecname hasten"

print(h_bind.game_str())
# /bind H "powexecname hasten"

Very rarely do we want to just execute hasten alone. More often we’d like to auto cast that it to keep it always active. Let’s create a new bind that does just that.

# Create a bind on the H key
new_h_bind = Bind(key="H")

# Add the auto power command for hasten
new_h_bind.commands.add_auto_power("hasten")

print(new_h_bind.get_str())
# H "powexecauto hasten"

But of course we already have a bind on the H key! Let’s instead add a modifier to the trigger so we can use both binds independently.

new_h_bind.trigger.modifier = "SHIFT"

print(new_h_bind.get_str())
# SHIFT+H "powexecauto hasten"

There we go! We can always access the trigger property of the bind to customize the key and modifier that make up the overall trigger. Note that we also could have just specified the modifier when we first created the bind (e.g., Bind(“SHIFT+H”)).

So far we’ve only added single commands to our binds, let’s try adding more to make this bind more interesting.

# Add more commands to the bind
new_h_bind.commands.add_command("say It's time for speed!")

print(new_h_bind.get_str())
# SHIFT+H "powexecauto hasten$$say It's time for speed!"

And just like that we get a bind with multiple commands, automatically separated by the proper in-game command separator ($$).

We can similarly create objects via the Macro class just as above, however instead of their commands being bound to a key, they’d be bound to an automatically created in-game button. Optionally, we can create MacroSlot objects which will create their macro button at a specific slot, or MacroImage objects which create macros with a custom icon.

The CommandGroup class is also available to create objects that just help group multiple slash commands. Think of these as just a bind without the trigger part. While they may not be used as much as binds or macros, they do have interesting use in bind files.

Bind Files

Getting back to our example, while we could just call each bind’s game_str method to get the command to load the new binds, let’s instead move on to the BindFile class which can help us manage and publish our binds. We can define a blank bind file like so:

from CityOfBinds import BindFile

# Create a blank bind file
bind_file = BindFile()

Just like we added commands to our binds, we can add binds to our bind file.

# Add binds to the bind file
bind_file.add_bind(h_bind)
bind_file.add_bind(new_h_bind)

print(bind_file.preview())
# H "powexecname hasten"
# SHIFT+H "powexecauto hasten$$say It's time for speed!"

As we can see by printing out the preview, both binds are now part of the bind file. We can now write this bind file to disk so we can load it in-game.

# Write bind file to disk
bind_file.write_to_file("C:/path/to/Homecoming/settings/live/hasten_binds.txt")

We now have a bind file written to our city of heroes default folder! If you’re not familiar with the default folder go ahead and readup up on it as it’ll make future file management easier. Default Folder

Now in game we can just enter /bindloadfile hasten_binds.txt into the chat window and our new binds will be loaded and ready to use! Note that if you specificed a different path other than the default folder, you’d have to bindloadfile that entire path to that the file.

BindFile also supports adding Macros and CommandGroups, so feel free to explore those as well! In the case of loading macros, unless it’s a MacroSlot object, every time the bind file is loaded, the macros will be added to the first available slot in your power tray.

In the case of CommandGroups, this allows arbitrary command execution when loading a bind file.

Custom Rotating Binds

The bind file we created in the previous example, while nice, is limited in scope in what we can actually do with binds. Let’s get into more advanced tools to create a rotating bind that allows us to automatically put multiple powers on auto cast as we just move around in the game.

As we will be creating binds on the traditional movement keys (WASD), we can use a special bind class called WASDBind to help manage the actual movement slash commands that are needed to move the character around. We can define a WASDBind like so:

from CityOfBinds import WASDBind

# Create a WASD bind system
w_hasten_bind = WASDBind("W")

print(w_hasten_bind.get_str())
# W "+forward"

As we can see, the WASDBind automatically adds the proper movement command for the W key. We can add more commands to it just as before:

# Add the auto power command for hasten
w_hasten_bind.commands.add_auto_power("hasten")

print(w_hasten_bind.get_str())
# W "+forward$$powexecauto hasten"

Now this bind will not only move the character forward, it’ll also ensure hasten is on auto cast! Talk about set and forget! But we can go even further, let’s go ahead and create a few more auto case W binds, as well as add each bind to its own bind file.

from CityOfBinds import BindFile

# Create more WASD binds with different auto powers
w_insp_bind = WASDBind("W")
w_insp_bind.commands.add_auto_power("inner inspiration")

w_domination_bind = WASDBind("W")
w_domination_bind.commands.add_auto_power("domination")

print(w_insp_bind.get_str())
# W "+forward$$powexecauto inner inspiration"

print(w_domination_bind.get_str())
# W "+forward$$powexecauto domination"

hasten_bf = BindFile().add_bind(w_hasten_bind)
insp_bf = BindFile().add_bind(w_insp_bind)
domination_bf = BindFile().add_bind(w_domination_bind)

Our three bind files will now allow us to put multiple powers on auto cast, however only one of the W binds will be active at one time. We need a way to make it so pressing W we will also load the new bind for W to enable auto casting of the next power.

We can always just use the add_command method to add a hard coded bindloadfile command to each bind, however, you’d have to keep track of file names and paths yourself. Instead let’s use a special class called BindFileGraph to logically link the bind files:

from CityOfBinds import BindFileGraph

# Create a bind file graph to manage the rotation
bfg = BindFileGraph()

# Add bind files to the graph
bfg.add_bind_file(hasten_bf) # assigned index 0
bfg.add_bind_file(insp_bf) # assigned index 1
bfg.add_bind_file(domination_bf) # assigned index 2

# Link the bind files in a rotation
bfg.loop([0, 1, 2])
# Creates a logical connection between files:
# 0 -> 1 -> 2 -> 0

The above code shows how to add bind files to the bind file graph, as well as a special linking method to create a logical loop between the list of file indexes. Note that when files are added to the bind file graph, they are automatically assigned an index which we can reference for linking purposes.

Once the logical links are created, we can now publish the bind files using a publishing class called BGFPublisher. This class will take care of adding the proper bindloadfile commands to each bind based on the logical links we created in the bind file graph.

from CityOfBinds import BGFPublisher

# Create a publisher for the bind file graph
publisher = BGFPublisher(bfg)

# Publish the bind files to disk
publisher.publish_bind_files(
    parent_folder_name="auto_powers",
    directory="C:/path/to/Homecoming/settings/live"
)
# C:/path/to/Homecoming/settings/live/auto_powers/0.txt
#   W "+forward$$powexecauto hasten$$bindloadfilesilent auto_powers/1.txt"
# C:/path/to/Homecoming/settings/live/auto_powers/1.txt
#   W "+forward$$powexecauto inner inspiration$$bindloadfilesilent auto_powers/2.txt"
# C:/path/to/Homecoming/settings/live/auto_powers/2.txt
#   W "+forward$$powexecauto domination$$bindloadfilesilent auto_powers/0.txt"

And just like before we can go in-game and load the first bind file with /bindloadfile auto_powers/0.txt. From there on out, as we move forward with the W key, the bind files will automatically rotate and ensure all powers will cycle between auto cast!

Built In Rotating Binds

Everything we’ve done up till now was to show how the core components of the library work together to create complex rotating binds. However, to make things easier, there are built in rotating bind classes that take care of the bind file graph and publisher for you.

Let’s again create a bind system to auto cast multiple powers, but let’s instead enable this on all WASD movement keys this time, as well as add even more powers. We can use the WASDRotatingBind class to help us with this.

from CityOfBinds import WASDRotatingBind

# Create a rotating bind system
wasd_bind = WASDRotatingBind()

# Add powers you want automatically turned on
wasd_bind.wasd_bind_template.add_auto_power_pool([
    "hasten", "inner inspiration", "domination", "mutation", "ageless core epiphany"
])

# Generate the bind files
wasd_bind.publish_bind_files(
    parent_folder_name="better_auto_powers",
    directory="C:/path/to/Homecoming/settings/live"
)
# C:/path/to/Homecoming/settings/live/better_auto_powers/0.txt
#   W "+forward$$powexecauto hasten$$bindloadfilesilent better_auto_powers/1.txt"
#   A "+left$$powexecauto hasten$$bindloadfilesilent better_auto_powers/1.txt"
#   S "+right$$powexecauto hasten$$bindloadfilesilent better_auto_powers/1.txt"
#   D "+backward$$powexecauto hasten$$bindloadfilesilent better_auto_powers/1.txt"
# C:/path/to/Homecoming/settings/live/better_auto_powers/1.txt
#   W "+forward$$powexecauto inner inspiration$$bindloadfilesilent better_auto_powers/2.txt"
#   A "+left$$powexecauto inner inspiration$$bindloadfilesilent better_auto_powers/2.txt"
#   S "+right$$powexecauto inner inspiration$$bindloadfilesilent better_auto_powers/2.txt"
#   D "+backward$$powexecauto inner inspiration$$bindloadfilesilent better_auto_powers/2.txt"
# C:/path/to/Homecoming/settings/live/better_auto_powers/2.txt
#   W "+forward$$powexecauto domination$$bindloadfilesilent better_auto_powers/3.txt"
#   A "+left$$powexecauto domination$$bindloadfilesilent better_auto_powers/3.txt"
#   S "+right$$powexecauto domination$$bindloadfilesilent better_auto_powers/3.txt"
#   D "+backward$$powexecauto domination$$bindloadfilesilent better_auto_powers/3.txt"
# C:/path/to/Homecoming/settings/live/better_auto_powers/3.txt
#   W "+forward$$powexecauto mutation$$bindloadfilesilent better_auto_powers/4.txt"
#   A "+left$$powexecauto mutation$$bindloadfilesilent better_auto_powers/4.txt"
#   S "+right$$powexecauto mutation$$bindloadfilesilent better_auto_powers/4.txt"
#   D "+backward$$powexecauto mutation$$bindloadfilesilent better_auto_powers/4.txt"
# C:/path/to/Homecoming/settings/live/better_auto_powers/4.txt
#   W "+forward$$powexecauto ageless core epiphany$$bindloadfilesilent better_auto_powers/0.txt"
#   A "+left$$powexecauto ageless core epiphany$$bindloadfilesilent better_auto_powers/0.txt"
#   S "+right$$powexecauto ageless core epiphany$$bindloadfilesilent better_auto_powers/0.txt"
#   D "+backward$$powexecauto ageless core epiphany$$bindloadfilesilent better_auto_powers/0.txt"

And in just a few lines of code, we have a full WASD rotating bind system that will auto cast multiple powers as we move around Paragon City!

I’ve tried to add a lot of useful rotating bind classes to help with common use cases. Feel free to explore the documentation for more features!

Advanced Rotating Bind Customization

If any of the built in rotating bind classes don’t fit your needs, you can of course always generate any type of custom rotating bind using the BindFileGraph and BGFPublisher classes as shown in the previous section.

All built in rotating binds allow extracting the underlying BindFileGraph to help with any customizations you may need. From there you can extend the graph as needed before publishing. For example:

from CityOfBinds import Bind, BindFile, RotatingBind, BindFileGraph, BGFPublisher

# Create a bind file to switch languages with relevant binds
language_switcher = BindFile()
language_switcher.add_bind(
    Bind("F1").commands.add_command("say Switching to English!")
)
language_switcher.add_bind(
    Bind("F2").commands.add_command("say Cambiando a Español!")
)

# Create a bind file graph and add our language switcher
bfg = BindFileGraph()
bfg.add_bind_file(language_switcher) # assigned index 0

# Create two rotating bind systems for different languages
english_hello = RotatingBind("H").add_command_arguments_pool([
    "say", ["Hello!", "Hi!", "What's up?"]
])
spanish_hello = RotatingBind("H").add_command_arguments_pool([
    "say", ["Hola!", "Buenos días!", "¿Qué tal?"]
])

# Get the underlying bind file graphs
english_bfg = english_hello.get_bind_file_graph()
spanish_bfg = spanish_hello.get_bind_file_graph()

# Extend the rotating bind created bind file graphs onto the main graph
bfg.extend(english_bfg).extend(spanish_bfg)
# file indexes 0, 1, 2 from english_bfg auto increment to 1, 2, 3 in bfg
# file indexes 0, 1, 2 from spanish_bfg auto increment to 4, 5, 6 in bfg

# Add custom links to make everything work together
bfg.link(0, 1, load_conditions={on_trigger:"F1"}) # link language switch to english hello
bfg.link(0, 4, load_conditions={on_trigger:"F2"}) # link language switch to spanish hola

# Publish the bind files
publisher = BGFPublisher(bfg)
publisher.publish_bind_files(
    parent_folder_name="language_hello_binds",
    directory="C:/path/to/Homecoming/settings/live"
)
# /bindloadfile language_hello_binds/0.txt to load the language switcher

While there is a lot more going on in this example, this shows how one might leverage the built in rotating bind classes to form a bind system which allows your character to cycle greetings by pressing the H key with the additional functionality of changing those greetings’ languages via the F1 and F2 keys.

🔧 Development Setup

If you want to contribute or modify the library:

# Clone the repository
git clone https://github.com/yourusername/city-of-binds.git
cd city-of-binds

# Install in development mode with dev dependencies
pip install -e ".[dev]"

# Run tests
pytest

# Format code
black .
isort .

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

cityofbinds-0.5.9.tar.gz (67.8 kB view details)

Uploaded Source

Built Distribution

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

cityofbinds-0.5.9-py3-none-any.whl (76.0 kB view details)

Uploaded Python 3

File details

Details for the file cityofbinds-0.5.9.tar.gz.

File metadata

  • Download URL: cityofbinds-0.5.9.tar.gz
  • Upload date:
  • Size: 67.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.2

File hashes

Hashes for cityofbinds-0.5.9.tar.gz
Algorithm Hash digest
SHA256 96edb905cb7a8a55f469597b28b7266fab68995f076e40a79644389a3de8973c
MD5 de1f18f777d17beca2da3e894a22cf3e
BLAKE2b-256 01be2856f551bff9a53878475e01723d3cb52886282f4acc3cb9943fb4891d94

See more details on using hashes here.

File details

Details for the file cityofbinds-0.5.9-py3-none-any.whl.

File metadata

  • Download URL: cityofbinds-0.5.9-py3-none-any.whl
  • Upload date:
  • Size: 76.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.2

File hashes

Hashes for cityofbinds-0.5.9-py3-none-any.whl
Algorithm Hash digest
SHA256 78734f9217f8fdc81965d95cd58d1cc1ccc86bea628ba05986751a35482210c6
MD5 b846ace43381ba858116a2e4eea02c35
BLAKE2b-256 74d564cd55b820658fb69e9a87929c95a364f115ca60507cef394bba805ccf07

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