Skip to content

Commit 636ddef

Browse files
committed
completely rewrite python wrapper using generated interop
1 parent 48154d4 commit 636ddef

27 files changed

+2471
-968
lines changed

.github/workflows/python.yml

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
name: Python
2+
3+
on: [push, pull_request]
4+
5+
jobs:
6+
mypy:
7+
runs-on: ubuntu-latest
8+
steps:
9+
- uses: actions/checkout@v4
10+
11+
- name: setup python
12+
uses: actions/setup-python@v4
13+
with:
14+
python-version: "3.13"
15+
16+
- name: install dependencies
17+
run: |
18+
pip install mypy cffi types-cffi
19+
20+
- name: run mypy
21+
run: mypy python
22+
23+
pyright:
24+
runs-on: ubuntu-latest
25+
steps:
26+
- uses: actions/checkout@v4
27+
28+
- name: setup python
29+
uses: actions/setup-python@v4
30+
with:
31+
python-version: "3.13"
32+
33+
- name: install dependencies
34+
run: |
35+
pip install pyright cffi types-cffi
36+
37+
- name: run pyright
38+
run: pyright python
File renamed without changes.

python/bot/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from .bot import Bot
2+
3+
__all__ = ["Bot"]

python/bot/bot.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import random
2+
from uwapi import *
3+
4+
class Bot:
5+
is_configured: bool = False
6+
work_step: int = 0 # save some cpu cycles by splitting work over multiple steps
7+
8+
def __init__(self):
9+
uw_events.on_update(self.on_update)
10+
11+
def attack_nearest_enemies(self):
12+
own_units = [x for x in uw_world.entities().values() if x.own() and x.Unit is not None and x.proto().data.get("dps", 0) > 0]
13+
if not own_units:
14+
return
15+
enemy_units = [x for x in uw_world.entities().values() if x.enemy() and x.Unit is not None]
16+
if not enemy_units:
17+
return
18+
for own in own_units:
19+
if len(uw_commands.orders(own.id)) == 0:
20+
enemy = min(enemy_units, key=lambda x: uw_map.distance_estimate(own.pos(), x.pos()))
21+
uw_commands.order(own.id, uw_commands.fight_to_entity(enemy.id))
22+
23+
def assign_random_recipes(self):
24+
for own in uw_world.entities().values():
25+
if not own.own() or own.Unit is None or own.Recipe is not None:
26+
continue
27+
recipes = own.proto().data.get("recipes", [])
28+
if recipes:
29+
recipe = random.choice(recipes)
30+
uw_commands.set_recipe(own.id, recipe)
31+
32+
def configure(self):
33+
if self.is_configured:
34+
return
35+
self.is_configured = True
36+
37+
uw_game.set_player_name("bot-cs")
38+
uw_game.player_join_force(0) # create new force
39+
uw_game.set_force_color(1, 0, 0)
40+
# todo choose race
41+
42+
def on_update(self, tick: int, stepping: bool):
43+
if uw_game.game_state() == GameState.Session:
44+
self.configure()
45+
return
46+
47+
if not stepping:
48+
return
49+
50+
self.work_step += 1
51+
match self.work_step % 10: # save some cpu cycles by splitting work over multiple steps
52+
case 1:
53+
self.attack_nearest_enemies()
54+
case 5:
55+
self.assign_random_recipes()
56+
57+
def run(self):
58+
uw_game.log_info("bot-py start")
59+
if not uw_game.try_reconnect():
60+
uw_game.set_connect_start_gui(True)
61+
if not uw_game.connect_environment():
62+
uw_game.connect_new_server()
63+
uw_game.log_info("bot-py done")

python/bot/main.py

Lines changed: 0 additions & 92 deletions
This file was deleted.

python/bot/requirements.txt

Lines changed: 0 additions & 1 deletion
This file was deleted.

python/main.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from uwapi import UwapiLibrary
2+
from bot import Bot
3+
4+
if __name__ == "__main__":
5+
with UwapiLibrary():
6+
Bot().run()

python/uwapi/__init__.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
2+
# THIS FILE IS GENERATED BY SCRIPT
3+
# DO NOT MODIFY
4+
5+
from .interop import *
6+
from .admin import uw_admin
7+
from .commands import uw_commands
8+
from .entity import Entity, INVALID
9+
from .events import uw_events
10+
from .game import uw_game
11+
from .library import UwapiLibrary
12+
from .map import uw_map
13+
from .prototypes import uw_prototypes
14+
from .world import uw_world
15+
16+
__all__ = ["uw_admin","uw_commands","Entity","INVALID","uw_events","uw_game","UwapiLibrary","uw_map","uw_prototypes","uw_world","Severity","LogCallback","ConnectionState","MyPlayer","AssistConfig","PerformanceStatistics","OrderType","OrderPriority","Order","Orders","Ids","Priority","Ping","PathState","ForeignPolicy","ChatTarget","ProtoComponent","OwnerComponent","ControllerComponent","PositionComponent","UnitState","UnitComponent","LifeComponent","ManaComponent","MoveComponent","AimComponent","RecipeComponent","RecipeStatisticsComponent","LogisticsTimestampComponent","PriorityComponent","AmountComponent","AttachmentComponent","PingComponent","PlayerState","PlayerConnectionClass","PlayerComponent","PlayerAiConfigComponent","ForceState","ForceComponent","ForceDetailsComponent","ForeignPolicyComponent","DiplomacyProposalComponent","GameState","ShootingUnit","ShootingData","ShootingArray","ExplosionData","ExplosionsArray","TaskType","MapState","MapInfo","MapStartingPosition","MapStartingPositionsArray","Tile","Cluster","ClustersDistancesQuery","ClustersDistancesResult","PrototypeType","MyForceStatistics","UnitUpgrades","Overview","OverviewExtract","UnitPathfindingQuery","UnitPathfindingResult"]

python/uwapi/admin.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
from .interop import *
2+
3+
INVALID: int = 0xFFFFFFFF
4+
5+
class Admin:
6+
_instance = None
7+
8+
def __new__(cls):
9+
if cls._instance is None:
10+
cls._instance = super().__new__(cls)
11+
return cls._instance
12+
13+
def get_lobby_id(self) -> int:
14+
return uw_interop.uwGetLobbyId()
15+
16+
def get_user_id(self) -> int:
17+
return uw_interop.uwGetUserId()
18+
19+
def get_server_port(self) -> int:
20+
return uw_interop.uwGetServerPort()
21+
22+
def set_map_selection(self, path: str) -> None:
23+
uw_interop.uwAdminSetMapSelection(path)
24+
25+
def start_game(self) -> None:
26+
uw_interop.uwAdminStartGame()
27+
28+
def terminate_game(self) -> None:
29+
uw_interop.uwAdminTerminateGame()
30+
31+
def set_game_speed(self, speed: float) -> None:
32+
uw_interop.uwAdminSetGameSpeed(speed)
33+
34+
def set_weather_speed(self, speed: float, offset: float) -> None:
35+
uw_interop.uwAdminSetWeatherSpeed(speed, offset)
36+
37+
def add_ai(self) -> None:
38+
uw_interop.uwAdminAddAi()
39+
40+
def kick_player(self, player_id: int) -> None:
41+
uw_interop.uwAdminKickPlayer(player_id)
42+
43+
def player_set_admin(self, player_id: int, admin: bool) -> None:
44+
uw_interop.uwAdminPlayerSetAdmin(player_id, admin)
45+
46+
def player_set_name(self, player_id: int, name: str) -> None:
47+
uw_interop.uwAdminPlayerSetName(player_id, name)
48+
49+
def player_join_force(self, player_id: int, force_id: int) -> None:
50+
uw_interop.uwAdminPlayerJoinForce(player_id, force_id)
51+
52+
def force_join_team(self, force_id: int, team: int) -> None:
53+
uw_interop.uwAdminForceJoinTeam(force_id, team)
54+
55+
def force_set_color(self, force_id: int, r: float, g: float, b: float) -> None:
56+
uw_interop.uwAdminForceSetColor(force_id, r, g, b)
57+
58+
def force_set_race(self, force_id: int, race_proto: int) -> None:
59+
uw_interop.uwAdminForceSetRace(force_id, race_proto)
60+
61+
def send_suggested_camera_focus(self, position: int) -> None:
62+
uw_interop.uwAdminSendSuggestedCameraFocus(position)
63+
64+
def set_automatic_suggested_camera_focus(self, enabled: bool) -> None:
65+
uw_interop.uwAdminSetAutomaticSuggestedCameraFocus(enabled)
66+
67+
def send_chat(self, msg: str, flags, id: int = INVALID) -> None:
68+
uw_interop.uwAdminSendChat(msg, flags, id)
69+
70+
def send_ping(self, position: int, ping, id: int) -> None:
71+
uw_interop.uwAdminSendPing(position, ping, id)
72+
73+
uw_admin = Admin()
File renamed without changes.

0 commit comments

Comments
 (0)