"""The lgbot plugin's frame logic (tick) against a fake minqlx: deaths, gaps, targets, acceleration.""" import importlib.util, logging, math, os, sys, types, unittest PLUGIN = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "home", "minqlx-plugins", "lgbot.py") BOT_ID = 90071996842377216 HUMAN_ID = 76561198000000000 class V: def __init__(self, x, y, z): self.x, self.y, self.z = x, y, z class FakePlayer: def __init__(self, cid, name, steam_id, pos): self.id, self.clean_name, self.steam_id = cid, name, steam_id self.team, self.is_alive, self.health = "free", True, 100 self.pos, self.vel = list(pos), [0.0, 0.0, 0.0] self.sets = [] def position(self): return V(*self.pos) def velocity(self, **kw): if kw: self.vel = [kw.get("x", self.vel[0]), kw.get("y", self.vel[1]), kw.get("z", self.vel[2])] self.sets.append(tuple(self.vel)) return True return V(*self.vel) class World: """Stands in for the server: players, cvars, a clock; moves players by their velocity.""" def __init__(self): self.players, self.cvars, self.now = [], {}, 1000.0 def frame(self, plugin, dt=0.025): self.now += dt plugin.handle_frame() for p in self.players: p.pos[0] += p.vel[0] * 0.85 * dt # friction eats ~15%, like the measured server p.pos[1] += p.vel[1] * 0.85 * dt p.vel[0] *= 0.85 p.vel[1] *= 0.85 def load(world): fake = types.ModuleType("minqlx") class Plugin: def __init__(self): self.logger = logging.getLogger("lgbot-test") def set_cvar_once(self, name, value): world.cvars.setdefault(name, value) def get_cvar(self, name): return world.cvars.get(name) def add_hook(self, event, fn): pass def players(self): return list(world.players) def log_exception(plugin=None): raise # surface plugin errors in the test fake.Plugin, fake.log_exception = Plugin, log_exception sys.modules["minqlx"] = fake try: spec = importlib.util.spec_from_file_location("lgbot_under_test", PLUGIN) mod = importlib.util.module_from_spec(spec) spec.loader.exec_module(mod) finally: del sys.modules["minqlx"] mod.time = types.SimpleNamespace(time=lambda: world.now) plugin = mod.lgbot() plugin.strafer.rng.seed(11) if hasattr(plugin.strafer.rng, "seed") else None return mod, plugin def sideways_sign(bot, human): # sign of the bot's velocity across the bot->human line dx, dy = human.pos[0] - bot.pos[0], human.pos[1] - bot.pos[1] return 1 if (-dy * bot.vel[0] + dx * bot.vel[1]) > 0 else -1 class Tick(unittest.TestCase): def setUp(self): self.w = World() self.bot = FakePlayer(0, "lgbot", BOT_ID, (0, 0, 0)) self.human = FakePlayer(1, "player", HUMAN_ID, (450, 0, 0)) self.w.players = [self.bot, self.human] self.w.cvars["lgbot_move"] = "5" self.mod, self.plugin = load(self.w) def run_frames(self, n): for _ in range(n): self.w.frame(self.plugin) def test_steers_when_human_in_range(self): self.run_frames(40) self.assertTrue(self.bot.sets) def test_human_named_lgbot_is_left_alone(self): self.bot.steam_id = HUMAN_ID self.run_frames(40) self.assertEqual(self.bot.sets, []) def test_strafes_both_ways_after_respawn(self): self.run_frames(80) self.bot.is_alive = False self.run_frames(400) # 10 s dead self.bot.is_alive = True self.bot.pos = [0, 0, 0] signs = [] for _ in range(400): self.w.frame(self.plugin) signs.append(sideways_sign(self.bot, self.human)) self.assertLess(max(signs.count(1), signs.count(-1)) / 400.0, 0.75) def test_strafes_both_ways_after_walking_in(self): self.human.pos = [3000, 0, 0] # far: AI walks self.run_frames(400) self.human.pos = [450, 0, 0] signs = [] for _ in range(400): self.w.frame(self.plugin) signs.append(sideways_sign(self.bot, self.human)) self.assertLess(max(signs.count(1), signs.count(-1)) / 400.0, 0.75) def test_velocity_changes_like_a_player_not_instantly(self): self.run_frames(400) sets = self.bot.sets comp = self.mod.FRICTION_COMP biggest = max(math.hypot(b[0] - a[0] * 0.85, b[1] - a[1] * 0.85) for a, b in zip(sets, sets[1:])) # per frame: at most the acceleration step (plus friction compensation), never a 640 u/s flip self.assertLess(biggest, self.mod.ACCEL * 0.025 * comp + 0.15 * 320 * comp + 1) def test_no_target_means_no_steering(self): self.human.team = "spectator" self.run_frames(40) self.assertEqual(self.bot.sets, []) if __name__ == "__main__": unittest.main()