lgbot: never take over or kick a human named lgbot; live values in show; direction debug
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
d2fb4be349
commit
aac104206b
3 files changed
+81
-13
No files matched your search
+36
-6
@@ -7,7 +7,7 @@
|
||||
Aim is the bot's skill level (addbot lgbot N). Movement is the lgbot_* cvars the lgbot plugin reads.
|
||||
The last setup is saved in home/lgbot.json.
|
||||
"""
|
||||
import json, os, sys
|
||||
import json, os, re, sys
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
sys.path.insert(0, os.path.join(HERE, "..", "home", "minqlx-plugins"))
|
||||
@@ -99,9 +99,18 @@ def prompt_setup(st, input_fn=input, print_fn=print):
|
||||
return {"aim": aim, "move": move, "details": details}
|
||||
|
||||
|
||||
def bot_present(status_text):
|
||||
def _named_lgbot(status_text):
|
||||
parsed = parse_rcon_status(status_text)
|
||||
return bool(parsed) and any(p[2].lower() == "lgbot" for p in parsed[1])
|
||||
return [p for p in parsed[1] if p[2].lower() == "lgbot"] if parsed else []
|
||||
|
||||
|
||||
def bot_present(status_text):
|
||||
return any(p[3] == "bot" for p in _named_lgbot(status_text))
|
||||
|
||||
|
||||
def impostor(status_text):
|
||||
"""A human calling themselves lgbot: "kick lgbot" would hit them, so don't send it."""
|
||||
return any(p[3] != "bot" for p in _named_lgbot(status_text))
|
||||
|
||||
|
||||
def build_commands(st, readd, bot_in):
|
||||
@@ -114,6 +123,11 @@ def build_commands(st, readd, bot_in):
|
||||
return cmds
|
||||
|
||||
|
||||
def cvar_value(reply, name):
|
||||
m = re.search(r'"%s" is:"([^"]*)"' % re.escape(name), reply)
|
||||
return m.group(1) if m else None
|
||||
|
||||
|
||||
def describe(st):
|
||||
d = ", ".join("%s %s" % (k, v) for k, v in sorted(st["details"].items()))
|
||||
return "aim %d, movement %d%s" % (st["aim"], st["move"], " (custom: %s)" % d if d else "")
|
||||
@@ -125,9 +139,19 @@ def server():
|
||||
return lambda cmd: qlrcon.run(s, cmd)
|
||||
|
||||
|
||||
def refuse_impostor(status_text):
|
||||
if impostor(status_text):
|
||||
print("a player (not a bot) is named lgbot: rename or kick them first, nothing changed")
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def apply(st, old_aim):
|
||||
run = server()
|
||||
bot_in = bot_present(run("status"))
|
||||
status = run("status")
|
||||
if refuse_impostor(status):
|
||||
return 1
|
||||
bot_in = bot_present(status)
|
||||
for cmd in build_commands(st, readd=(not bot_in or st["aim"] != old_aim), bot_in=bot_in):
|
||||
run(cmd)
|
||||
ok = bot_present(run("status"))
|
||||
@@ -139,12 +163,18 @@ def main(argv, input_fn=input):
|
||||
arg = argv[1] if len(argv) > 1 else ""
|
||||
st = load_state()
|
||||
if arg == "off":
|
||||
server()("kick lgbot")
|
||||
run = server()
|
||||
if refuse_impostor(run("status")):
|
||||
return 1
|
||||
run("kick lgbot")
|
||||
print("lgbot removed (setup kept, ./lgbot --last brings it back)")
|
||||
return 0
|
||||
if arg == "show":
|
||||
run = server()
|
||||
print("saved setup: " + describe(st))
|
||||
print("bot is " + ("in" if bot_present(server()("status")) else "not in"))
|
||||
live = ["%s %s" % (k, cvar_value(run("lgbot_" + k), "lgbot_" + k)) for k in ["move"] + KEYS]
|
||||
print("live (-1 = from the movement level): " + ", ".join(live))
|
||||
print("bot is " + ("in" if bot_present(run("status")) else "not in"))
|
||||
return 0
|
||||
if arg == "--last":
|
||||
return apply(st, old_aim=None) # old_aim None: always re-add so the aim level is right
|
||||
|
||||
+18
-1
@@ -1,7 +1,7 @@
|
||||
import math, os, random, sys, unittest
|
||||
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "home", "minqlx-plugins"))
|
||||
from lgbot import (PRESETS, KEYS, resolve, Strafer, wanted_range, steer, fix_z, pick_target, to_int, is_bot)
|
||||
from lgbot import (PRESETS, KEYS, resolve, Strafer, wanted_range, steer, fix_z, pick_target, to_int, is_bot, is_lgbot)
|
||||
|
||||
|
||||
def preset(n, **kw):
|
||||
@@ -129,6 +129,12 @@ class Misc(unittest.TestCase):
|
||||
self.assertTrue(is_bot(0))
|
||||
self.assertFalse(is_bot(76561198000000000)) # real SteamID64
|
||||
|
||||
def test_is_lgbot_needs_bot_id(self):
|
||||
self.assertTrue(is_lgbot("lgbot", 90071996842377216))
|
||||
self.assertTrue(is_lgbot("LGBot", 90071996842377217))
|
||||
self.assertFalse(is_lgbot("lgbot", 76561198000000000)) # a human calling themselves lgbot
|
||||
self.assertFalse(is_lgbot("hunter", 90071996842377216))
|
||||
|
||||
def test_to_int(self):
|
||||
self.assertEqual(to_int("5"), 5)
|
||||
self.assertEqual(to_int(" -1 "), -1)
|
||||
@@ -207,6 +213,17 @@ class Parsing(unittest.TestCase):
|
||||
self.assertEqual(v, 2)
|
||||
self.assertEqual(len(said), 2)
|
||||
|
||||
def test_human_named_lgbot_is_not_the_bot(self):
|
||||
status = STATUS_WITH_BOT.replace("lgbot 0 bot ", "lgbot 0 1.2.3.4:5")
|
||||
self.assertFalse(setup.bot_present(status))
|
||||
self.assertTrue(setup.impostor(status))
|
||||
self.assertFalse(setup.impostor(STATUS_WITH_BOT))
|
||||
|
||||
def test_cvar_value(self):
|
||||
reply = 'zmq RCON command from x: lgbot_fake' + chr(10) + '"lgbot_fake" is:"60" default:"-1"'
|
||||
self.assertEqual(setup.cvar_value(reply, "lgbot_fake"), "60")
|
||||
self.assertIsNone(setup.cvar_value("(no output: ok, or server down)", "lgbot_fake"))
|
||||
|
||||
def test_bot_present(self):
|
||||
self.assertTrue(setup.bot_present(STATUS_WITH_BOT))
|
||||
self.assertFalse(setup.bot_present(STATUS_WITH_BOT.replace("lgbot ", "Hunter")))
|
||||
|
||||
Reference in new issue
Block a user