0
|
1 import datetime
|
|
2 import os
|
|
3 import pdb
|
|
4 import subprocess
|
|
5 import sys
|
|
6 import time
|
|
7 import logging
|
|
8 import traceback
|
|
9 from typing import Any, Optional, Tuple
|
|
10
|
|
11 from javascript import On, Once, console, require
|
|
12
|
|
13 logging.basicConfig(
|
|
14 level=logging.INFO,
|
|
15 format="%(asctime)s [%(levelname)s] [line %(lineno)s] %(message)s")
|
|
16 log = logging.getLogger()
|
|
17
|
|
18 mineflayer = require("mineflayer", "latest")
|
|
19 Vec3 = require("vec3").Vec3
|
|
20 registry = require('prismarine-registry')('1.19.3')
|
|
21 Block = require("prismarine-block")(registry)
|
|
22
|
|
23 host, port, rconPort = ('bang', 25665, 25675)
|
|
24
|
|
25 mfbot = mineflayer.createBot({
|
|
26 "host": host,
|
|
27 "port": port,
|
|
28 "username": 'termbanator',
|
|
29 'auth': 'offline',
|
|
30 'version': '1.19.3',
|
|
31 })
|
|
32 log.info('created')
|
|
33
|
|
34
|
|
35 class Player:
|
|
36
|
|
37 def __init__(self, player):
|
|
38 self._p = player
|
|
39 if self._p is None:
|
|
40 raise TypeError(f'player was {player}')
|
|
41 self.username = self._p.username
|
|
42
|
|
43 def __repr__(self):
|
|
44 return f'Player({self.username})'
|
|
45
|
|
46 def position(self) -> Tuple[float, float, float]:
|
|
47 if self._p.entity is None:
|
|
48 return getDistantPlayerPos(self.username)
|
|
49 pos = self._p.entity.position
|
|
50 return (round(pos.x, 2), round(pos.y, 2), round(pos.z, 2))
|
|
51
|
|
52
|
|
53 def getDistantPlayerPos(username) -> Tuple[float, float, float]:
|
|
54 out = subprocess.check_output([
|
|
55 "/home/drewp/Downloads/mcrcon/mcrcon", "-H", host, "-P",
|
|
56 str(rconPort), "-p", "111", f"/data get entity @p[name={username}] Pos"
|
|
57 ],
|
|
58 encoding='utf8')
|
|
59 words = out.split("the following entity data: ")[1].split('[')[1].split(
|
|
60 ']')[0].split(',')
|
|
61 return tuple(round(float(v.rstrip('d')), 2) for v in words)
|
|
62
|
|
63
|
|
64 class Bot:
|
|
65
|
|
66 def __init__(self, mineflayer_bot: Any):
|
|
67 self._bot = mineflayer_bot
|
|
68 self.me = Player(self._bot)
|
|
69 mcData = require('minecraft-data')(self._bot.version)
|
|
70 self.pf_module = require('mineflayer-pathfinder')
|
|
71 self._bot.loadPlugin(self.pf_module.pathfinder)
|
|
72
|
|
73 self.movements = self.pf_module.Movements(self._bot, mcData)
|
|
74 self.movements.allow1by1towers = True
|
|
75 self.movements.canDig = False
|
|
76 self.movements.scafoldingBlocks.push(
|
|
77 self._bot.registry.itemsByName['dirt'].id)
|
|
78 self.movements.maxDropDown = 300
|
|
79 self.movements.allowFreeMotion = True
|
|
80 self.movements.canOpenDoors = True
|
|
81
|
|
82 self._bot.removeAllListeners('chat')
|
|
83
|
|
84 self.walkStartTime = 0
|
|
85 self.waitUntil = time.time() + 2
|
|
86 self.target: Optional[Player] = None
|
|
87 self.announcedToTarget = False
|
|
88
|
|
89 def chat(self, txt):
|
|
90 log.info(f'say {txt!r}')
|
|
91 self._bot.chat(txt)
|
|
92
|
|
93 def teleport(self, x, y, z):
|
|
94 self._bot.chat(f'/tp {x} {y} {z}')
|
|
95
|
|
96 def setPathfinderGoal(self, x, y, z, rng):
|
|
97 pos = self.me.position()
|
|
98 log.info(f'now at {pos}, pathfinding to {x} {y} {z} {rng=}')
|
|
99 goal = self.pf_module.goals.GoalNear(x, y, z, rng)
|
|
100 self._bot.pathfinder.setMovements(self.movements)
|
|
101 self._bot.pathfinder.setGoal(goal)
|
|
102 self.walkStartTime = time.time()
|
|
103
|
|
104 def isMoving(self) -> bool:
|
|
105 return self._bot.pathfinder.isMoving()
|
|
106
|
|
107 def stopPathfinder(self):
|
|
108 log.info("Stopping pathfinder")
|
|
109 self._bot.pathfinder.stop()
|
|
110
|
|
111 def ban(self, player: Player, reason: str = "good night"):
|
|
112 self.chat(f'/ban {player.username} {reason}')
|
|
113
|
|
114 def quit(self):
|
|
115 self._bot.quit('done')
|
|
116 os.kill(os.getpid(), 15)
|
|
117
|
|
118 ########## below here doesn't use self._bot ##########
|
|
119
|
|
120 def onLogin(self):
|
|
121 self.chat("Beware the termbanator")
|
|
122 # self.teleport(-160, 200, 280)
|
|
123
|
|
124 def walkTo(self, player: Player):
|
|
125 self.target = player
|
|
126 self.announcedToTarget = False
|
|
127 log.info(f'walk to {self.target}')
|
|
128 pos = player.position()
|
|
129 self.setPathfinderGoal(pos[0], pos[1], pos[2], rng=3)
|
|
130
|
|
131 def stillWalking(self, walkMinTime=3):
|
|
132 if self.walkStartTime and time.time(
|
|
133 ) < self.walkStartTime + walkMinTime:
|
|
134 return True
|
|
135 return self.isMoving()
|
|
136
|
|
137 def update(self, waitBetweenTargets=9):
|
|
138 try:
|
|
139 now = time.time()
|
|
140
|
|
141 if self.waitUntil > 0 and now < self.waitUntil:
|
|
142 log.debug(f'waiting {round(self.waitUntil-now, 1)} more sec')
|
|
143 return
|
|
144 self.waitUntil = 0
|
|
145
|
|
146 if self.stillWalking():
|
|
147 pos = self.me.position()
|
|
148 log.info(f'still moving to {self.target}: {pos=}')
|
|
149 return
|
|
150
|
|
151 self.stopPathfinder()
|
|
152
|
|
153 if self.target:
|
|
154 if not self.announcedToTarget:
|
|
155 minsLeft = round(
|
|
156 (self.getTodayEndTime() - time.time()) / 60, 1)
|
|
157
|
|
158 if minsLeft > 0:
|
|
159 mins = 'minutes' if minsLeft != 1 else 'minute'
|
|
160 self.chat(
|
|
161 f'hi {self.target.username} - {minsLeft} {mins} left'
|
|
162 )
|
|
163 else:
|
|
164 self.ban(self.target)
|
|
165
|
|
166 self.announcedToTarget = True
|
|
167 self.sleepFor(waitBetweenTargets)
|
|
168 return
|
|
169
|
|
170 log.info('follow next player')
|
|
171
|
|
172 next = players.after(self.target)
|
|
173 try:
|
|
174 self.walkTo(next)
|
|
175 except ValueError as e:
|
|
176 log.warning(repr(e))
|
|
177 self.sleepFor(8)
|
|
178 except EveryoneGone:
|
|
179 bot.quit()
|
|
180 except Exception as e:
|
|
181 traceback.print_exc()
|
|
182 log.warning(repr(e))
|
|
183 self.sleepFor(60)
|
|
184
|
|
185 def getTodayEndTime(self) -> float:
|
|
186 current_date = datetime.datetime.now().date()
|
|
187 end_datetime = datetime.datetime.combine(current_date,
|
|
188 datetime.time(19, 0))
|
|
189
|
|
190 return end_datetime.timestamp()
|
|
191
|
|
192 def sleepFor(self, sec):
|
|
193 log.info(f'sleeping for {sec}')
|
|
194 self.waitUntil = time.time() + sec
|
|
195
|
|
196
|
|
197 class EveryoneGone(ValueError):
|
|
198 pass
|
|
199
|
|
200
|
|
201 class Players:
|
|
202
|
|
203 def __init__(self, mineflayer_bot: Any):
|
|
204 self._b = mineflayer_bot
|
|
205
|
|
206 def byName(self, name) -> Player:
|
|
207 # only works for 'nearby' players- useless
|
|
208 return Player(self._b.players[name])
|
|
209
|
|
210 def after(self, player: Optional[Player]) -> Player:
|
|
211 otherPlayers = [p for p in self._b.players if p != 'termbanator']
|
|
212 log.info(f'{otherPlayers=}')
|
|
213 if not otherPlayers:
|
|
214 log.info('everyone is gone')
|
|
215 raise EveryoneGone()
|
|
216 # import pdb;pdb.set_trace()
|
|
217 prevPlayer = player.username if player else None
|
|
218
|
|
219 try:
|
|
220 i = otherPlayers.index(prevPlayer)
|
|
221 except (AttributeError, ValueError):
|
|
222 i = 0
|
|
223 i = (i + 1) % len(otherPlayers)
|
|
224
|
|
225 return self.byName(otherPlayers[i])
|
|
226
|
|
227
|
|
228 bot = Bot(mfbot)
|
|
229 players = Players(mfbot)
|
|
230
|
|
231
|
|
232 @On(mfbot, "login")
|
|
233 def login(this):
|
|
234 bot.onLogin()
|
|
235
|
|
236
|
|
237 @On(mfbot, 'chat')
|
|
238 def handleMsg(this, sender, message, *args):
|
|
239 if sender and (sender != 'termbanator'):
|
|
240 pass
|
|
241
|
|
242
|
|
243 @On(mfbot, 'time')
|
|
244 def onTime(this):
|
|
245 bot.update()
|
|
246
|
|
247
|
|
248 @On(mfbot, 'message')
|
|
249 def message(emitter, msg, *args):
|
|
250 log.info(f'got msg event {msg.toString()}') |