-
Notifications
You must be signed in to change notification settings - Fork 72
/
esagent.py
160 lines (143 loc) · 7.93 KB
/
esagent.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
import melee
import math
from Strategies.bait import Bait
from melee.enums import ProjectileType, Action, Button, Character
class ESAgent():
"""
Expert system agent for SmashBot.
This is the "manually programmed" TAS-looking agent.
"""
def __init__(self, dolphin, smashbot_port, opponent_port, controller, difficulty=4):
self.smashbot_port = smashbot_port
self.opponent_port = opponent_port
self.controller = controller
self.framedata = melee.framedata.FrameData()
self.logger = dolphin.logger
self.difficulty = difficulty
self.ledge_grab_count = 0
self.tech_lockout = 0
self.meteor_jump_lockout = 0
self.meteor_ff_lockout = 0
self.powershielded_last = False
self.strategy = Bait(self.logger,
self.controller,
self.framedata,
self.difficulty)
def act(self, gamestate):
if self.smashbot_port not in gamestate.players:
self.controller.release_all()
return
# Figure out who our opponent is
# Opponent is the closest player that is a different costume
if len(gamestate.players) > 2:
opponents = []
for i, player in gamestate.players.items():
if i == self.smashbot_port:
continue
if not gamestate.is_teams or (player.team_id != gamestate.players[self.smashbot_port].team_id):
opponents.append(i)
nearest_dist = 1000
nearest_port = 1
for i, player in gamestate.players.items():
if len(opponents) > 0 and i not in opponents:
continue
xdist = gamestate.players[self.smashbot_port].position.x - player.position.x
ydist = gamestate.players[self.smashbot_port].position.y - player.position.y
dist = math.sqrt((xdist**2) + (ydist**2))
if dist < nearest_dist:
nearest_dist = dist
nearest_port = i
self.opponent_port = nearest_port
gamestate.distance = nearest_dist
# Pick the right climber to be the opponent
if gamestate.players[self.opponent_port].nana is not None:
xdist = gamestate.players[self.opponent_port].nana.position.x - gamestate.players[self.smashbot_port].position.x
ydist = gamestate.players[self.opponent_port].nana.position.y - gamestate.players[self.smashbot_port].position.y
dist = math.sqrt((xdist**2) + (ydist**2))
if dist < gamestate.distance:
gamestate.distance = dist
popo = gamestate.players[self.opponent_port]
gamestate.players[self.opponent_port] = gamestate.players[self.opponent_port].nana
gamestate.players[self.opponent_port].nana = popo
knownprojectiles = []
for projectile in gamestate.projectiles:
# Held turnips and link bombs
if projectile.type in [ProjectileType.TURNIP, ProjectileType.LINK_BOMB, ProjectileType.YLINK_BOMB]:
if projectile.subtype in [0, 4, 5]:
continue
# Charging arrows
if projectile.type in [ProjectileType.YLINK_ARROW, ProjectileType.FIRE_ARROW, \
ProjectileType.LINK_ARROW, ProjectileType.ARROW]:
if projectile.speed.x == 0 and projectile.speed.y == 0:
continue
# Pesticide
if projectile.type == ProjectileType.PESTICIDE:
continue
# Ignore projectiles owned by us
if projectile.owner == self.smashbot_port:
continue
if projectile.type not in [ProjectileType.UNKNOWN_PROJECTILE, ProjectileType.PEACH_PARASOL, \
ProjectileType.FOX_LASER, ProjectileType.SHEIK_CHAIN, ProjectileType.SHEIK_SMOKE]:
knownprojectiles.append(projectile)
gamestate.projectiles = knownprojectiles
# Yoshi shield animations are weird. Change them to normal shield
if gamestate.players[self.opponent_port].character == Character.YOSHI:
if gamestate.players[self.opponent_port].action in [melee.Action.NEUTRAL_B_CHARGING, melee.Action.NEUTRAL_B_FULL_CHARGE, melee.Action.LASER_GUN_PULL]:
gamestate.players[self.opponent_port].action = melee.Action.SHIELD
# Tech lockout
if gamestate.players[self.smashbot_port].controller_state.button[Button.BUTTON_L]:
self.tech_lockout = 40
else:
self.tech_lockout -= 1
self.tech_lockout = max(0, self.tech_lockout)
# Jump meteor cancel lockout
if gamestate.players[self.smashbot_port].controller_state.button[Button.BUTTON_Y] or \
gamestate.players[self.smashbot_port].controller_state.main_stick[1] > 0.8:
self.meteor_jump_lockout = 40
else:
self.meteor_jump_lockout -= 1
self.meteor_jump_lockout = max(0, self.meteor_jump_lockout)
# Firefox meteor cancel lockout
if gamestate.players[self.smashbot_port].controller_state.button[Button.BUTTON_B] and \
gamestate.players[self.smashbot_port].controller_state.main_stick[1] > 0.8:
self.meteor_ff_lockout = 40
else:
self.meteor_ff_lockout -= 1
self.meteor_ff_lockout = max(0, self.meteor_ff_lockout)
# Keep a ledge grab count
if gamestate.players[self.opponent_port].action == Action.EDGE_CATCHING and gamestate.players[self.opponent_port].action_frame == 1:
self.ledge_grab_count += 1
if gamestate.players[self.opponent_port].on_ground:
self.ledge_grab_count = 0
if gamestate.frame == -123:
self.ledge_grab_count = 0
gamestate.custom["ledge_grab_count"] = self.ledge_grab_count
gamestate.custom["tech_lockout"] = self.tech_lockout
gamestate.custom["meteor_jump_lockout"] = self.meteor_jump_lockout
gamestate.custom["meteor_ff_lockout"] = self.meteor_ff_lockout
if gamestate.players[self.smashbot_port].action in [Action.SHIELD_REFLECT, Action.SHIELD_STUN]:
if gamestate.players[self.smashbot_port].is_powershield:
self.powershielded_last = True
elif gamestate.players[self.smashbot_port].hitlag_left > 0:
self.powershielded_last = False
gamestate.custom["powershielded_last"] = self.powershielded_last
# Let's treat Counter-Moves as invulnerable. So we'll know to not attack during that time
countering = False
if gamestate.players[self.opponent_port].character in [Character.ROY, Character.MARTH]:
if gamestate.players[self.opponent_port].action in [Action.MARTH_COUNTER, Action.MARTH_COUNTER_FALLING]:
# We consider Counter to start a frame early and a frame late
if 4 <= gamestate.players[self.opponent_port].action_frame <= 30:
countering = True
if gamestate.players[self.opponent_port].character == Character.PEACH:
if gamestate.players[self.opponent_port].action in [Action.UP_B_GROUND, Action.DOWN_B_STUN]:
if 4 <= gamestate.players[self.opponent_port].action_frame <= 30:
countering = True
if countering:
gamestate.players[self.opponent_port].invulnerable = True
gamestate.players[self.opponent_port].invulnerability_left = max(29 - gamestate.players[self.opponent_port].action_frame, gamestate.players[self.opponent_port].invulnerability_left)
# Platform drop is fully actionable. Don't be fooled
if gamestate.players[self.opponent_port].action == Action.PLATFORM_DROP:
gamestate.players[self.opponent_port].hitstun_frames_left = 0
self.strategy.step(gamestate,
gamestate.players[self.smashbot_port],
gamestate.players[self.opponent_port])