EldenClient API
The EldenClient class provides game-specific functionality for Elden Ring.
EldenClient
EldenClient
Bases: SiphonClient
Client for the Elden Ring game.
Source code in eldengym/client/elden_client.py
| class EldenClient(SiphonClient):
"""
Client for the Elden Ring game.
"""
def __init__(self, host="localhost:50051", **kwargs):
super().__init__(host, **kwargs)
self.scenarios = {
"margit": {
"boss_name": "Margit",
"fog_wall_location": (
19.958229064941406,
-11.990748405456543,
-7.051832675933838,
),
}
}
## =========== Initialization methods ===========
# def load_config_from_file(self, config_filepath):
# """
# Load process configuration from a TOML file and send to server.
# This is a convenience method that parses the config file and calls
# set_process_config with the extracted data.
# Args:
# config_filepath: str or Path, path to TOML config file
# Returns:
# SetProcessConfigResponse from server
# Raises:
# FileNotFoundError: If config file doesn't exist
# ValueError: If config file is malformed
# Example:
# >>> client = EldenClient()
# >>> response = client.load_config_from_file("config.toml")
# >>> if response.success:
# ... print("Config loaded successfully!")
# """
# process_name, process_window_name, attributes = parse_config_file(config_filepath)
# return self.set_process_config(process_name, process_window_name, attributes)
def _resolve_config_path(self, config_filepath):
"""
Resolve config file path relative to package root.
Args:
config_filepath: str or Path, can be:
- Absolute path: used as-is
- Relative path: resolved relative to package root (eldengym/)
- Filename only: looked up in eldengym/files/configs/
Returns:
Path: Resolved absolute path to config file
"""
config_path = Path(config_filepath)
# If absolute path, use it directly
if config_path.is_absolute():
return config_path
# Get package root (eldengym/)
package_root = Path(__file__).parent.parent
# If it's just a filename (no directory parts), look in configs directory
if len(config_path.parts) == 1:
config_path = package_root / "files" / "configs" / config_path
else:
# Relative path - resolve from package root
config_path = package_root / config_path
return config_path.resolve()
def load_config_from_file(self, config_filepath, wait_time=2):
"""
Complete initialization sequence: load config, initialize memory, input, and capture.
This is a convenience method that performs all initialization steps at once,
mirroring the 'init' command from the C++ client.
Args:
config_filepath: str or Path, path to TOML config file. Can be:
- Absolute path: /full/path/to/config.toml
- Relative to package: files/configs/ER_1_16_1.toml
- Filename only: ER_1_16_1.toml (searches in eldengym/files/configs/)
wait_time: int, seconds to wait after loading config before initializing subsystems
Returns:
dict with keys 'config', 'memory', 'input', 'capture' containing the responses
Raises:
FileNotFoundError: If config file doesn't exist
ValueError: If config file is malformed
RuntimeError: If any initialization step fails
Example:
>>> client = EldenClient()
>>> # All these work from any directory:
>>> results = client.load_config_from_file("ER_1_16_1.toml")
>>> results = client.load_config_from_file("files/configs/ER_1_16_1.toml")
>>> results = client.load_config_from_file("/absolute/path/to/config.toml")
"""
import time
results = {}
# Resolve config path
resolved_path = self._resolve_config_path(config_filepath)
# Parse config file
print(f"Loading config from: {resolved_path}")
process_name, process_window_name, attributes = parse_config_file(resolved_path)
print(
f"Config loaded - Process: {process_name}, Window: {process_window_name}, "
f"Attributes: {len(attributes)}"
)
# Send config to server
print("Sending configuration to server...")
config_response = self.set_process_config(
process_name, process_window_name, attributes
)
results["config"] = config_response
if not config_response.success:
raise RuntimeError(
f"Failed to set process config: {config_response.message}"
)
print(f"Server response: {config_response.message}")
# Wait for process to be ready
if wait_time > 0:
print(f"Waiting {wait_time} seconds for process to be ready...")
time.sleep(wait_time)
# Initialize memory
print("Initializing memory subsystem...")
memory_response = self.initialize_memory()
results["memory"] = memory_response
if not memory_response.success:
raise RuntimeError(
f"Failed to initialize memory: {memory_response.message}"
)
print(f"Server response: {memory_response.message}")
if hasattr(memory_response, "process_id") and memory_response.process_id > 0:
print(f"Process ID: {memory_response.process_id}")
# Initialize input
print("Initializing input subsystem...")
input_response = self.initialize_input()
results["input"] = input_response
if not input_response.success:
raise RuntimeError(f"Failed to initialize input: {input_response.message}")
print(f"Server response: {input_response.message}")
# Initialize capture
print("Initializing capture subsystem...")
capture_response = self.initialize_capture()
results["capture"] = capture_response
if not capture_response.success:
raise RuntimeError(
f"Failed to initialize capture: {capture_response.message}"
)
print(f"Server response: {capture_response.message}")
if hasattr(capture_response, "window_width") and hasattr(
capture_response, "window_height"
):
if capture_response.window_width > 0 and capture_response.window_height > 0:
print(
f"Window size: {capture_response.window_width}x{capture_response.window_height}"
)
print("\n=== Initialization Complete! ===")
print("All subsystems initialized successfully.")
return results
def launch_game(self):
"""
Launch the game.
"""
launch_response = self.execute_command(
"start_protected_game.exe",
args=None,
working_directory="C:\Program Files (x86)\Steam\steamapps\common\ELDEN RING\Game",
)
if not launch_response.success:
raise RuntimeError(f"Failed to launch game: {launch_response.message}")
print(f"Server response: {launch_response.message}")
if hasattr(launch_response, "process_id") and launch_response.process_id > 0:
print(f"Process ID: {launch_response.process_id}")
return launch_response
def bypass_menu(self):
"""
Bypass the menu.
"""
self.send_key(["ENTER"], 200, 0)
sleep(1)
self.send_key(["ENTER"], 200, 0)
sleep(1)
self.send_key(["ENTER"], 200, 0)
## =========== Player methods ===========
@property
def player_hp(self):
"""
Get the health of the player.
"""
return self.get_attribute("HeroHp")
@property
def player_max_hp(self):
"""
Get the maximum health of the player.
"""
return self.get_attribute("HeroMaxHp")
def set_player_hp(self, hp):
"""
Set the health of the player.
"""
self.set_attribute("HeroHp", hp)
@property
def local_player_coords(self):
"""
Get the location of the player.
"""
local_x = self.get_attribute("HeroLocalPosX")
local_y = self.get_attribute("HeroLocalPosY")
local_z = self.get_attribute("HeroLocalPosZ")
return local_x, local_y, local_z
@property
def global_player_coords(self):
"""
Get the location of the player.
"""
global_x = self.get_attribute("HeroGlobalPosX")
global_y = self.get_attribute("HeroGlobalPosY")
global_z = self.get_attribute("HeroGlobalPosZ")
return global_x, global_y, global_z
@property
def player_animation_id(self):
"""
Get the animation id of the player.
"""
return self.get_attribute("HeroAnimId")
## =========== Target methods ===========
@property
def target_hp(self):
"""
Get the health of the target.
"""
return self.get_attribute("NpcHp")
@property
def target_max_hp(self):
"""
Get the maximum health of the target.
"""
return self.get_attribute("NpcMaxHp")
def set_target_hp(self, hp):
"""
Set the health of the target.
"""
self.set_attribute("NpcHp", hp)
@property
def local_target_coords(self):
"""
Get the location of the target.
"""
local_x = self.get_attribute("NpcLocalPosX")
local_y = self.get_attribute("NpcLocalPosY")
local_z = self.get_attribute("NpcLocalPosZ")
return local_x, local_y, local_z
@property
def global_target_coords(self):
"""
Get the location of the target.
"""
global_x = self.get_attribute("NpcGlobalPosX")
global_y = self.get_attribute("NpcGlobalPosY")
global_z = self.get_attribute("NpcGlobalPosZ")
return global_x, global_y, global_z
@property
def target_animation_id(self):
"""
Get the animation id of the target.
"""
return self.get_attribute("NpcAnimId")
## =========== Helper methods ===========
@property
def target_player_distance(self):
"""
Get the distance between the player and the target.
"""
player_x, player_y, player_z = self.local_player_coords
target_x, target_y, target_z = self.global_target_coords
return np.linalg.norm(
[player_x - target_x, player_y - target_y, player_z - target_z]
)
def teleport(self, x, y, z):
"""
Teleport the player to the given coordinates.
"""
# FIXME: Close range teleport, need to check MapId for long range teleport.
local_x, local_y, local_z = self.local_player_coords
global_x, global_y, global_z = self.global_player_coords
self.set_attribute("HeroLocalPosX", local_x + (x - global_x))
self.set_attribute("HeroLocalPosY", local_y + (y - global_y))
self.set_attribute("HeroLocalPosZ", local_z + (z - global_z))
def set_game_speed(self, speed):
"""
Set the game speed.
"""
self.set_attribute("gameSpeedFlag", True)
self.set_attribute("gameSpeedVal", speed)
def reset_game(self):
"""
Reset the game by setting the player's hp to 0.
"""
self.set_player_hp(0)
sleep(
20
) # FIXME: This is a hack to wait for the game to reset, doesn't work well.
def start_scenario(self, scenario_name="Margit"):
"""
Start the scenario with the given scenario name.
"""
# FIXME: This is a hack to start boss fight. Need to check fogwall state. or use another method.
x, y, z = self.scenarios[scenario_name]["fog_wall_location"]
self.teleport(x, y, z)
self.move_mouse(1000, 0, 1)
sleep(2)
self.send_key(["W", "E"], 200, 200)
sleep(2)
self.send_key(["B"], 200)
|
global_player_coords
property
Get the location of the player.
global_target_coords
property
Get the location of the target.
local_player_coords
property
Get the location of the player.
local_target_coords
property
Get the location of the target.
player_animation_id
property
Get the animation id of the player.
player_hp
property
Get the health of the player.
player_max_hp
property
Get the maximum health of the player.
target_animation_id
property
Get the animation id of the target.
target_hp
property
Get the health of the target.
target_max_hp
property
Get the maximum health of the target.
target_player_distance
property
Get the distance between the player and the target.
Bypass the menu.
Source code in eldengym/client/elden_client.py
| def bypass_menu(self):
"""
Bypass the menu.
"""
self.send_key(["ENTER"], 200, 0)
sleep(1)
self.send_key(["ENTER"], 200, 0)
sleep(1)
self.send_key(["ENTER"], 200, 0)
|
launch_game
Launch the game.
Source code in eldengym/client/elden_client.py
| def launch_game(self):
"""
Launch the game.
"""
launch_response = self.execute_command(
"start_protected_game.exe",
args=None,
working_directory="C:\Program Files (x86)\Steam\steamapps\common\ELDEN RING\Game",
)
if not launch_response.success:
raise RuntimeError(f"Failed to launch game: {launch_response.message}")
print(f"Server response: {launch_response.message}")
if hasattr(launch_response, "process_id") and launch_response.process_id > 0:
print(f"Process ID: {launch_response.process_id}")
return launch_response
|
load_config_from_file
load_config_from_file(config_filepath, wait_time=2)
Complete initialization sequence: load config, initialize memory, input, and capture.
This is a convenience method that performs all initialization steps at once,
mirroring the 'init' command from the C++ client.
Parameters:
| Name |
Type |
Description |
Default |
config_filepath
|
|
str or Path, path to TOML config file. Can be:
- Absolute path: /full/path/to/config.toml
- Relative to package: files/configs/ER_1_16_1.toml
- Filename only: ER_1_16_1.toml (searches in eldengym/files/configs/)
|
required
|
wait_time
|
|
int, seconds to wait after loading config before initializing subsystems
|
2
|
Returns:
| Type |
Description |
|
|
dict with keys 'config', 'memory', 'input', 'capture' containing the responses
|
Raises:
| Type |
Description |
FileNotFoundError
|
If config file doesn't exist
|
ValueError
|
If config file is malformed
|
RuntimeError
|
If any initialization step fails
|
Example
client = EldenClient()
All these work from any directory:
results = client.load_config_from_file("ER_1_16_1.toml")
results = client.load_config_from_file("files/configs/ER_1_16_1.toml")
results = client.load_config_from_file("/absolute/path/to/config.toml")
Source code in eldengym/client/elden_client.py
| def load_config_from_file(self, config_filepath, wait_time=2):
"""
Complete initialization sequence: load config, initialize memory, input, and capture.
This is a convenience method that performs all initialization steps at once,
mirroring the 'init' command from the C++ client.
Args:
config_filepath: str or Path, path to TOML config file. Can be:
- Absolute path: /full/path/to/config.toml
- Relative to package: files/configs/ER_1_16_1.toml
- Filename only: ER_1_16_1.toml (searches in eldengym/files/configs/)
wait_time: int, seconds to wait after loading config before initializing subsystems
Returns:
dict with keys 'config', 'memory', 'input', 'capture' containing the responses
Raises:
FileNotFoundError: If config file doesn't exist
ValueError: If config file is malformed
RuntimeError: If any initialization step fails
Example:
>>> client = EldenClient()
>>> # All these work from any directory:
>>> results = client.load_config_from_file("ER_1_16_1.toml")
>>> results = client.load_config_from_file("files/configs/ER_1_16_1.toml")
>>> results = client.load_config_from_file("/absolute/path/to/config.toml")
"""
import time
results = {}
# Resolve config path
resolved_path = self._resolve_config_path(config_filepath)
# Parse config file
print(f"Loading config from: {resolved_path}")
process_name, process_window_name, attributes = parse_config_file(resolved_path)
print(
f"Config loaded - Process: {process_name}, Window: {process_window_name}, "
f"Attributes: {len(attributes)}"
)
# Send config to server
print("Sending configuration to server...")
config_response = self.set_process_config(
process_name, process_window_name, attributes
)
results["config"] = config_response
if not config_response.success:
raise RuntimeError(
f"Failed to set process config: {config_response.message}"
)
print(f"Server response: {config_response.message}")
# Wait for process to be ready
if wait_time > 0:
print(f"Waiting {wait_time} seconds for process to be ready...")
time.sleep(wait_time)
# Initialize memory
print("Initializing memory subsystem...")
memory_response = self.initialize_memory()
results["memory"] = memory_response
if not memory_response.success:
raise RuntimeError(
f"Failed to initialize memory: {memory_response.message}"
)
print(f"Server response: {memory_response.message}")
if hasattr(memory_response, "process_id") and memory_response.process_id > 0:
print(f"Process ID: {memory_response.process_id}")
# Initialize input
print("Initializing input subsystem...")
input_response = self.initialize_input()
results["input"] = input_response
if not input_response.success:
raise RuntimeError(f"Failed to initialize input: {input_response.message}")
print(f"Server response: {input_response.message}")
# Initialize capture
print("Initializing capture subsystem...")
capture_response = self.initialize_capture()
results["capture"] = capture_response
if not capture_response.success:
raise RuntimeError(
f"Failed to initialize capture: {capture_response.message}"
)
print(f"Server response: {capture_response.message}")
if hasattr(capture_response, "window_width") and hasattr(
capture_response, "window_height"
):
if capture_response.window_width > 0 and capture_response.window_height > 0:
print(
f"Window size: {capture_response.window_width}x{capture_response.window_height}"
)
print("\n=== Initialization Complete! ===")
print("All subsystems initialized successfully.")
return results
|
reset_game
Reset the game by setting the player's hp to 0.
Source code in eldengym/client/elden_client.py
| def reset_game(self):
"""
Reset the game by setting the player's hp to 0.
"""
self.set_player_hp(0)
sleep(
20
) # FIXME: This is a hack to wait for the game to reset, doesn't work well.
|
set_game_speed
Set the game speed.
Source code in eldengym/client/elden_client.py
| def set_game_speed(self, speed):
"""
Set the game speed.
"""
self.set_attribute("gameSpeedFlag", True)
self.set_attribute("gameSpeedVal", speed)
|
set_player_hp
Set the health of the player.
Source code in eldengym/client/elden_client.py
| def set_player_hp(self, hp):
"""
Set the health of the player.
"""
self.set_attribute("HeroHp", hp)
|
set_target_hp
Set the health of the target.
Source code in eldengym/client/elden_client.py
| def set_target_hp(self, hp):
"""
Set the health of the target.
"""
self.set_attribute("NpcHp", hp)
|
start_scenario
start_scenario(scenario_name='Margit')
Start the scenario with the given scenario name.
Source code in eldengym/client/elden_client.py
| def start_scenario(self, scenario_name="Margit"):
"""
Start the scenario with the given scenario name.
"""
# FIXME: This is a hack to start boss fight. Need to check fogwall state. or use another method.
x, y, z = self.scenarios[scenario_name]["fog_wall_location"]
self.teleport(x, y, z)
self.move_mouse(1000, 0, 1)
sleep(2)
self.send_key(["W", "E"], 200, 200)
sleep(2)
self.send_key(["B"], 200)
|
teleport
Teleport the player to the given coordinates.
Source code in eldengym/client/elden_client.py
| def teleport(self, x, y, z):
"""
Teleport the player to the given coordinates.
"""
# FIXME: Close range teleport, need to check MapId for long range teleport.
local_x, local_y, local_z = self.local_player_coords
global_x, global_y, global_z = self.global_player_coords
self.set_attribute("HeroLocalPosX", local_x + (x - global_x))
self.set_attribute("HeroLocalPosY", local_y + (y - global_y))
self.set_attribute("HeroLocalPosZ", local_z + (z - global_z))
|
Initialization Methods
load_config_from_file(config_filepath, wait_time=2)
Complete initialization: load config, initialize memory, input, and capture subsystems.
Args:
- config_filepath (str | Path): Path to TOML config file
- Filename only: "ER_1_16_1.toml" (searches in eldengym/files/configs/)
- Relative path: "files/configs/ER_1_16_1.toml" (from package root)
- Absolute path: "/full/path/to/config.toml"
- wait_time (int): Seconds to wait after loading config (default: 2)
Returns:
- dict: Results with keys 'config', 'memory', 'input', 'capture'
Example:
from eldengym.client.elden_client import EldenClient
client = EldenClient(host="localhost:50051")
results = client.load_config_from_file("ER_1_16_1.toml")
print(f"Initialized: {results['memory'].success}")
launch_game()
Launch Elden Ring game executable.
Returns:
- ExecuteCommandResponse: Command execution result
Automatically bypass the main menu to load into the game.
Player Methods
Properties
player_hp
Get player's current HP.
hp = client.player_hp
print(f"HP: {hp}")
player_max_hp
Get player's maximum HP.
local_player_coords
Get player's local coordinates (x, y, z).
x, y, z = client.local_player_coords
global_player_coords
Get player's global coordinates (x, y, z).
player_animation_id
Get current player animation ID.
Methods
set_player_hp(hp)
Set player's HP.
client.set_player_hp(1000) # Set HP to 1000
teleport(x, y, z)
Teleport player to coordinates.
client.teleport(100.0, 200.0, 50.0)
Target/Boss Methods
Properties
target_hp
Get target's current HP.
target_max_hp
Get target's maximum HP.
local_target_coords
Get target's local coordinates (x, y, z).
global_target_coords
Get target's global coordinates (x, y, z).
target_animation_id
Get current target animation ID.
Methods
set_target_hp(hp)
Set target's HP.
client.set_target_hp(500) # Set boss HP to 500
Helper Methods
target_player_distance
Get distance between player and target.
distance = client.target_player_distance
print(f"Distance to boss: {distance:.2f}")
set_game_speed(speed)
Set game speed multiplier.
client.set_game_speed(2.0) # 2x speed
client.set_game_speed(0.5) # Half speed
reset_game()
Reset the game (kills player, triggers death/respawn).
start_scenario(scenario_name)
Start a boss fight scenario.
Args:
- scenario_name (str): Name of scenario (e.g., "margit")
client.start_scenario("margit")
Low-Level Methods
These methods are inherited from SiphonClient and provide direct game control:
send_key(keys, hold_time, delay_time) - Send keyboard input
move_mouse(delta_x, delta_y, steps) - Move mouse
toggle_key(key, toggle) - Press/release key
get_attribute(name) - Read memory value
set_attribute(name, value) - Write memory value
get_frame() - Capture game frame
execute_command(...) - Execute system command
See SiphonClient API for details.
Example: Complete Workflow
from eldengym.client.elden_client import EldenClient
# Create client
client = EldenClient(host="localhost:50051")
# Initialize everything
results = client.load_config_from_file("ER_1_16_1.toml", wait_time=2)
# Get player info
print(f"Player HP: {client.player_hp}/{client.player_max_hp}")
print(f"Boss HP: {client.target_hp}/{client.target_max_hp}")
print(f"Distance: {client.target_player_distance:.2f}")
# Control the game
client.send_key(["W"], 500) # Move forward for 500ms
client.send_key(["SPACE"], 100) # Jump
# Capture frame
frame = client.get_frame()
print(f"Frame shape: {frame.shape}")
# Clean up
client.close()