diff --git a/plugins/utilities.json b/plugins/utilities.json index 6f43960..f4b09fe 100644 --- a/plugins/utilities.json +++ b/plugins/utilities.json @@ -55,11 +55,11 @@ }, "sandbox": { "description": "Spawn, control and bots, change music, add teams, spawn objects, change game values in real time, and much more!", - "external_url": "", + "external_url": "https://brobordd.github.io/byBordd", "authors": [ { "name": "BrotherBoard", - "email": "thehero2012008@gmail.com", + "email": "brobordd@gmail.com", "discord": "BrotherBoard" } ], @@ -86,15 +86,16 @@ }, "updown": { "description": "Adds UP and DOWN buttons in party window to recall messages in chat.", - "external_url": "", + "external_url": "https://brobordd.github.io/byBordd", "authors": [ { "name": "BrotherBoard", - "email": "thehero2012008@gmail.com", + "email": "brobordd@gmail.com", "discord": "BrotherBoard" } ], "versions": { + "1.1.4": null, "1.1.3": { "api_version": 9, "commit_sha": "e604a3c", @@ -128,12 +129,12 @@ } }, "rejoin": { - "description": "Adds a button in pause menu, which rejoins current server once clicked! if didn't work, just click again and again. if still, then party is full.", - "external_url": "", + "description": "Deprecated - Better use Power plugin. Adds a button in pause menu, which rejoins current server once clicked! if didn't work, just click again and again. if still, then party is full.", + "external_url": "https://brobordd.github.io/byBordd", "authors": [ { "name": "BrotherBoard", - "email": "thehero2012008@gmail.com", + "email": "brobordd@gmail.com", "discord": "BrotherBoard" } ], @@ -164,17 +165,108 @@ } } }, - "topmsg": { - "description": "When chat is muted, see new chat messages on top right! (like kill logs)", - "external_url": "", + "power": { + "description": "With one click. Experimental. Adds a dev console tab with some features I find useful. Power is mainly focused on the multiplayer side. Can be considered a good tool to have around.", + "external_url": "https://brobordd.github.io/byBordd", "authors": [ { "name": "BrotherBoard", - "email": "thehero2012008@gmail.com", + "email": "brobordd@gmail.com", "discord": "BrotherBoard" } ], "versions": { + "2.7": null + } + } + }, + "plugtools": { + "description": "Live Plugin Action. Beta. Adds a dev console tab for plugin management. For full features, read the first lines in the py file, or look in the source.", + "external_url": "https://brobordd.github.io/byBordd", + "authors": [ + { + "name": "BrotherBoard", + "email": "brobordd@gmail.com", + "discord": "BrotherBoard" + } + ], + "versions": { + "1.5": null + } + } + }, + "camera": { + "description": "Say cheese. Adds a button to pause menu. Camera is advanced Camera allows you to change camera position and target with a very easy graphical visualization of how it would look like.", + "external_url": "https://brobordd.github.io/byBordd", + "authors": [ + { + "name": "BrotherBoard", + "email": "brobordd@gmail.com", + "discord": "BrotherBoard" + } + ], + "versions": { + "1.0": null + } + } + }, + "finder": { + "description": "Find anyone. Experimental. Useful if you are looking for someone, or just messing around. For full features, either check first lines of py file, or check source. Combine with Power plugin for better control.", + "external_url": "https://brobordd.github.io/byBordd", + "authors": [ + { + "name": "BrotherBoard", + "email": "brobordd@gmail.com", + "discord": "BrotherBoard" + } + ], + "versions": { + "1.0": null + } + } + }, + "path": { + "description": "Where it's going to be. Experimental. Path tries to predict the next position of bomb. Path relies on velocity to operate. Optionally pass spaz node (holder) to assist prediction.", + "external_url": "https://brobordd.github.io/byBordd", + "authors": [ + { + "name": "BrotherBoard", + "email": "brobordd@gmail.com", + "discord": "BrotherBoard" + } + ], + "versions": { + "1.0": null + } + } + }, + "replay": { + "description": "Simple replay player. Experimental. Adds a button to pause menu and watch menu. For full features, either read first lines of py file, or check source.", + "external_url": "https://brobordd.github.io/byBordd", + "authors": [ + { + "name": "BrotherBoard", + "email": "brobordd@gmail.com", + "discord": "BrotherBoard" + } + ], + "versions": { + "1.5": null + } + } + }, + "topmsg": { + "description": "When chat is muted, see new chat messages on top right! (like kill logs)", + "external_url": "https://brobordd.github.io/byBordd", + "authors": [ + { + "name": "BrotherBoard", + "email": "brobordd@gmail.com", + "discord": "BrotherBoard" + } + ], + "versions": { + "1.1.2": null, "1.1.1": { "api_version": 9, "commit_sha": "e604a3c", @@ -197,11 +289,11 @@ }, "sorry": { "description": "Send a random sorry to chat, preventing revenge attempts from teammates you kill by mistake.", - "external_url": "", + "external_url": "https://brobordd.github.io/byBordd", "authors": [ { "name": "BrotherBoard", - "email": "thehero2012008@gmail.com", + "email": "brobordd@gmail.com", "discord": "BrotherBoard" } ], @@ -2140,4 +2232,4 @@ } } } -} \ No newline at end of file +} diff --git a/plugins/utilities/camera.py b/plugins/utilities/camera.py new file mode 100755 index 0000000..cbb0a36 --- /dev/null +++ b/plugins/utilities/camera.py @@ -0,0 +1,586 @@ +# Copyright 2025 - Solely by BrotherBoard +# Bug? Feedback? Telegram >> @GalaxyA14user + +""" +Camera v1.0 - Say cheese. + +Adds a button to pause menu. Camera is advanced +Camera allows you to change camera position and +target with a very easy graphical visualization +of how it would look like. +""" + +from _babase import ( + get_display_resolution as GDR, + clipboard_is_supported as CIS, + set_camera_position as SCP, + clipboard_set_text as COPY, + set_camera_manual as SSCM, + set_camera_target as SCT +) +from bascenev1 import ( + get_foreground_host_activity as ga, + OutOfBoundsMessage, + gettexture as gbt, + getsound as gbs, + timer as tick, + Material, + getmesh, + newnode, + animate, + emitfx +) +from bauiv1 import ( + get_special_widget as gsw, + containerwidget as cw, + screenmessage as push, + buttonwidget as bw, + imagewidget as iw, + textwidget as tw, + gettexture as gt, + apptimer as teck, + getsound as gs, + app as APP +) +from bauiv1lib.ingamemenu import InGameMenuWindow as igm +from babase import Plugin, InputType as IT +from math import sqrt, dist + +class Camera: + __doc__ = 'A simple camera.' + __ins__ = None + __lst__ = None + __yes__ = False + def __init__(s) -> None: + c = s.__class__ + if c.__yes__: + note('Stopped camera!',True) + c.__ins__.done(talk=False) + return + c.__ins__ = s + c.__yes__ = True + if c.__lst__: SCM(False) + s.stage = 0 + p = (0,1,0) + s.tex = 'achievementCrossHair' + s.kids = [] + s.okay = [] + with ga().context: + s.M = Material() + s.M.add_actions( + conditions=(('they_are_older_than', 0)), + actions=( + ('modify_part_collision', 'collide', False), + ('modify_part_collision', 'physical', False), + ('modify_part_collision', 'friction', 0), + ('modify_part_collision', 'stiffness', 0), + ('modify_part_collision', 'damping', 0) + ) + ) + n = newnode( + 'prop', + delegate=s, + attrs={ + 'mesh': getmesh('tnt'), + 'color_texture': gbt(s.tex), + 'body': 'crate', + 'reflection': 'soft', + 'density': 4.0, + 'reflection_scale': [1.5], + 'shadow_size': 0.6, + 'position': p, + 'gravity_scale': 0, + 'materials': [s.M], + 'is_area_of_interest': True + } + ) + tick(0.15, animate(n,'mesh_scale',{0:2,0.1:0.5}).delete) + gbs('powerup01').play(position=p) + s.step = 0.01 + s.node = n + s.wait = 0.001 + s.mode = 4 + s.llr = s.lud = 0.0 + s.overlay = Overlay() + LN({ + 'UP_DOWN': lambda a: s.manage(a), + 'LEFT_RIGHT': lambda a: s.manage(a,1), + 'PICK_UP_PRESS': lambda: s.start(2), + 'JUMP_PRESS': lambda: s.start(0), + 'PICK_UP_RELEASE': lambda: s.stop(2), + 'JUMP_RELEASE': lambda: s.stop(0), + 'BOMB_PRESS': s.done, + 'BOMB_RELEASE': lambda: s.overlay.release(1), + 'PUNCH_PRESS': s.mark, + 'PUNCH_RELEASE': lambda: s.overlay.release(3), + }) + s.move() + """Write a tip""" + def tip(s,t,p,h='left',b=True): + n = newnode( + 'text', + attrs={ + 'in_world': True, + 'scale': 0.01, + 'flatness': 1, + 'color': (1,1,1), + 'shadow': 1.0, + 'position': p, + 'text': t, + 'h_align': h + } + ) + if b: s.kids.append(n) + return n + """Create a dot""" + def dot(s,p,b=True,tex='black'): + n = newnode( + 'prop', + delegate=s, + attrs={ + 'mesh': getmesh('tnt'), + 'color_texture': gbt(tex), + 'body': 'crate', + 'mesh_scale': 0.1, + 'position': p, + 'gravity_scale': 0, + 'materials': [s.M], + } + ) + if b: s.kids.append(n) + return n + """Draw a line""" + def line(s,p1,p2,i=2,tex='black'): + x1,y1,z1 = p1 + x2,y2,z2 = p2 + n = dist(p1,p2)*i + for i in range(1,int(n+1)): + t = i/n + x = x1+t*(x2-x1) + y = y1+t*(y2-y1) + z = z1+t*(z2-z1) + s.kids.append(s.dot((x,y,z),tex=tex)) + """Mark""" + def mark(s): + if not s.stage: + s.stage = 1 + p = s.getpos() + s.p1 = (p[0]-0.01,p[1],p[2]) + s.okay.append(s.dot(s.p1,b=False)) + s.okay.append(s.tip(f'Camera position\n{tuple([round(i,2) for i in s.p1])}',s.p1,b=False)) + else: + [i.delete() for i in s.kids] + s.kids.clear() + p2 = s.p2 = s.getpos() + + r = GDR() + w = r[0]/r[1] + h = 1 + + vd = sub(p2, s.p1) + vd_n = norm(vd) + + t_up = (0, 1, 0) + r_v = cross(vd_n, t_up) + r_v_n = norm(r_v) + + up_v = cross(r_v_n, vd_n) + up_v_n = norm(up_v) + + hw = w / 2.0 + hh = h / 2.0 + + tr = add(p2, add(scale(r_v_n, hw), scale(up_v_n, hh))) + tl = add(p2, add(scale(r_v_n, -hw), scale(up_v_n, hh))) + br = add(p2, add(scale(r_v_n, hw), scale(up_v_n, -hh))) + bl = add(p2, add(scale(r_v_n, -hw), scale(up_v_n, -hh))) + + s.line(s.p1, tr) + s.line(s.p1, tl) + s.line(s.p1, br) + s.line(s.p1, bl) + + m = 4 + j = {'tex':'crossOutMask'} + s.line(tr, tl, m, **j) + s.line(tl, bl, m, **j) + s.line(bl, br, m, **j) + s.line(br, tr, m, **j) + + s.tip(f'Your display\n{r[0]}x{r[1]} px\n{tuple([round(i,2) for i in p2])}',tr,'right') + s.stage = 2 + s.overlay.press(3) + tick(0.25, animate(s.node,'mesh_scale',{0:0.5,0.1:0.2,0.2:0.5}).delete) + gbs('gunCocking').play(position=s.node.position) + """Handle events""" + def handlemessage(s, m): + if isinstance(m, OutOfBoundsMessage): + p = s.getpos() + gbs('shatter').play(position=p) + emitfx( + scale=1, + count=30, + spread=0.1, + position=p, + chunk_type='ice' + ) + s.destroy() + note('Out of bounds!') + """Destroy""" + def destroy(s): + with ga().context: + n = s.node + s.mode = 2 + n.delete() + s.reset() + """Reset input""" + def reset(s): + s.__class__.__yes__ = False + me = getme() + if not me: return + me.resetinput() + with ga().context: me.actor.connect_controls_to_player() + [i.delete() for i in (s.kids+s.okay)] + """Manage movement""" + def manage(s,a,lr=0): + if lr: s.llr = a; return + s.lud = a + """Move""" + def move(s): + m = getme(1) + if (not m) or m._dead: s.destroy() + try: p = s.getpos() + except: + s.overlay.destroy() + return + s.setpos((p[0]+s.llr*s.step,p[1],p[2]-s.lud*s.step)) + s.overlay.up(*p,s.llr,s.lud) + SCT(*p) + teck(s.wait,s.move) + """Start elevating""" + def start(s,i): + s.overlay.press(i) + s.mode = i + s.loop(i) + """Keep elevating""" + def loop(s,i): + if s.mode != i: return + try: p = list(s.node.position) + except: return + p[1] += s.step if i else -s.step + s.node.position = tuple(p) + teck(s.wait, lambda: s.loop(i)) + """Stop elevating""" + def stop(s,i): + s.overlay.release(i) + s.mode = 4 + """Get position""" + def getpos(s): + return s.node.position + """Set position""" + def setpos(s,p): + s.node.position = p + """Done""" + def done(s,talk=True): + s.overlay.press(1) + s.overlay.destroy() + try: p = s.node.position + except: return + with ga().context: + gbs('laser').play(position=p) + tick(0.2,animate(s.node,'mesh_scale',{0:0.5,0.08:1,0.2:0}).delete) + tick(0.2,s.node.delete) + s.reset() + if s.stage > 1 and talk: + SCM(True) + SCP(*s.p1) + SCT(*s.p2) + var('lp1',s.p1) + var('lp2',s.p2) + nice('Applied!') + elif talk: + note('Incomplete camera setup\nNo changes applied.') + if s.__class__.__ins__ == s: s.__class__.__ins__ = None + +"""Controls overlay""" +class Overlay: + __lst__ = None + """Place nodes""" + def __init__(s): + s.__class__.__lst__ = str(ga()) + s.colors = [ + [(0.2,0.6,0.2),(0.4,1,0.4)], + [(0.6,0,0),(1,0,0)], + [(0.2,0.6,0.6),(0.4,1,1)], + [(0.6,0.6,0.2),(1,1,0.4)], + [(0.3,0.23,0.5),(0.2,0.13,0.3)] + ] + s.pics = [] + s.texts = [] + s.pos = [] + s.nub = [] + s.old = [0,0,0] + s.dead = False + with ga().context: + for i in range(4): + j = ['Jump','Bomb','PickUp','Punch'][i] + k = [600,650,600,550][i] + l = [170,220,270,220][i] + c = s.colors[i][0] + n = newnode( + 'image', + attrs={ + 'texture': gbt('button'+j), + 'absolute_scale': True, + 'position': (k,l), + 'scale': (60,60), + 'color': c + } + ) + s.pics.append(n) + j = ['Down','Done','Up','Mark'][i] + k = [600,680,600,515][i] + l = [115,220,325,220][i] + h = ['center','left','center','right'][i] + v = ['bottom','center','top','center'][i] + n = newnode( + 'text', + attrs={ + 'text': j, + 'position': (k,l), + 'color': c, + 'h_align': h, + 'v_align': v + } + ) + s.texts.append(n) + for i in range(3): + c = s.colors[[1,0,2][i]][0] + n = newnode( + 'text', + attrs={ + 'text': '0', + 'position': (640,155-30*i), + 'color': c, + 'h_align': 'left' + } + ) + s.pos.append(n) + s.np = (790,140) + for i in [0,1]: + j = [110,60][i] + n = newnode( + 'image', + attrs={ + 'texture': gbt('nub'), + 'absolute_scale': True, + 'position': s.np, + 'scale': (j,j), + 'color': s.colors[4][i] + } + ) + s.nub.append(n) + s.fade() + """Color overlays""" + def set(s,i,c): + s.pics[i].color = s.texts[i].color = c + """Color position""" + def pset(s,i,c): + s.pos[i].color = c + """Simulate pressed""" + def press(s,i): + s.set(i,s.colors[i][1]) + s.pics[i].opacity = 1.0 + """Simulate released""" + def release(s,i): + s.set(i,s.colors[i][0]) + s.pics[i].opacity = 0.7 + """Get all nodes""" + def nodes(s): + return s.pics+s.texts+s.pos+s.nub + """Update position""" + def up(s,x,y,z,lr,ud): + new = [x,y,z] + for i in range(3): + c = s.colors[[1,0,2][i]] + if s.old[i] == new[i]: s.pset(i,c[0]); continue + t = s.pos[i] + t.text = str(round(new[i],5)) + s.pset(i,c[1]) + s.old = new + [setattr(s.nub[i],'opacity',[[0.5,0.2],[0.7,0.3]][bool(lr or ud)][i]) for i in [0,1]] + p = s.np + m = sqrt(lr**2+ud**2) or 1 + d = 25*min(sqrt(lr**2+ud**2),1) + lr /= m + ud /= m + s.nub[1].position = (p[0]+lr*d,p[1]+ud*d) + """Fade""" + def fade(s,i=0): + if str(ga()) != s.__class__.__lst__: return + mem = s.nodes() + [tick(1, animate(n,'opacity',{0:i,0.5:abs(i-0.7)}).delete) for n in mem] + """Destroy overlay""" + def destroy(s): + if s.dead: return + s.dead = True + with ga().context: + tick(0.2,lambda:s.fade(0.7)) + tick(2,lambda: [n.delete() for n in s.nodes()]) + +# Mini tools +note = lambda t, b=False: (push(t,color=(1,1,0)),gs('block').play() if b else None) +nice = lambda t: (push(t,color=(0,1,0)),gs('dingSmallHigh').play()) +SCM = lambda b: (setattr(Camera,'__lst__',b),SSCM(b)) +scale = lambda v,s: (v[0]*s,v[1]*s,v[2]*s) +cross = lambda a,b: (a[1]*b[2]-a[2]*b[1],a[2]*b[0]-a[0]*b[2],a[0]*b[1]-a[1]*b[0]) +sub = lambda a,b: (a[0]-b[0],a[1]-b[1],a[2]-b[2]) +add = lambda a,b: (a[0]+b[0],a[1]+b[1],a[2]+b[2]) +def getme(actor=0): + for p in ga().players: + if p.sessionplayer.inputdevice.client_id == -1: + return p.actor if actor else p +def LN(d): me = getme(); [me.assigninput(getattr(IT,k), d[k]) for k in d] +def RESUME(): + u = APP.ui_v1 + c = APP.classic + c.resume() + u.clear_main_window() + [z() for z in c.main_menu_resume_callbacks] + c.main_menu_resume_callbacks.clear() +def norm(v): + a,b,c = v + l = sqrt(a**2+b**2+c**2) + return (0, 0, 0) if l == 0 else (a/l,b/l,c/l) +def var(s,v=None): + c = APP.config + s = 'cam_'+s + if v is None: return c.get(s,v) + c[s] = v + c.commit() + +# brobord collide grass +# ba_meta require api 9 +# ba_meta export babase.Plugin +class byBordd(Plugin): + has_settings_ui = lambda s: True + show_settings_ui = lambda s, src: s.ui(source=src) + col = (0.18,0.18,0.18) + def __init__(s): + o = igm._refresh_in_game + def e(f,*a,**k): + r = o(f,*a,**k) + b = bw( + label='', + size=(90,40), + button_type='square', + parent=f._root_widget, + color=(0.18,0.18,0.18), + position=(f._width-20,0), + ) + bw(b,on_activate_call=lambda:s.ui(source=b,main=True)) + iw( + size=(40,40), + texture=gt('tv'), + parent=f._root_widget, + position=(f._width-20,5) + ) + tw( + maxwidth=50, + text='Camera', + h_align='left', + parent=f._root_widget, + position=(f._width+15,0) + ) + return r + igm._refresh_in_game = e + """The UI""" + def ui(s,source=None,main=False): + s.main = main + off = source.get_screen_space_center() if source else (0,0) + w = cw( + color=s.col, + size=(350,305), + stack_offset=off, + transition='in_scale', + parent=gsw('overlay_stack'), + scale_origin_stack_offset=off + ) + s.back = lambda b=True: (cw(w,transition=['out_right','out_scale'][bool(source) and b]),gs('swish').play() if b else None) + cw(w,on_outside_click_call=s.back) + b = Camera.__yes__ + t = [ + ('Camera is ready!',(0,1,1)), + ('Camera is running!',(0,1,0)), + ][b] + tw( + parent=w, + text=t[0], + scale=1.5, + color=t[1], + h_align='center', + position=(155,250) + ) + for i in range(4): + j = [ + ('3D Camera mapper',s.start), + ('Last mapped config',s.load), + ('Last dev command',s.copy), + ('Reset all settings',s.reset) + ][i] + tw( + parent=w, + text=j[0], + maxwidth=195, + position=(20,30+55*i) + ) + k = [ + (['Start','Stop'][b],'cursor'), + ('Load','achievementOutline'), + ('Copy','file'), + ('Reset','replayIcon') + ][i] + bw( + parent=w, + label=k[0], + color=s.col, + size=(120,50), + icon=gt(k[1]), + enable_sound=not i, + textcolor=(1,1,1), + button_type='square', + on_activate_call=j[1], + position=(220,20+55*i) + ) + """Gather last""" + def gather(s): + return var('lp1'),var('lp2') + """Reset""" + def reset(s): + SCM(False) + nice('Resetored original settings!') + """Copy last""" + def copy(s): + if not CIS(): note('Unsupported!',True); return + g = s.gather() + if not g[1]: note('Apply something first!',True); return + g = [tuple([round(i,2) for i in j]) for j in g] + COPY(f'from _babase import set_camera_manual as SCM, set_camera_target as SCT, set_camera_position as SCP; SCM(True); SCP(*{g[0]}); SCT(*{g[1]})') + nice('Copied command!\nPaste it in dev console anytime to load config!') + """Load last""" + def load(s): + g = s.gather() + if not g[1]: note('Apply something first!',True); return + if Camera.__yes__: note('Stop camera first!',True); return + SCM(True) + SCP(*g[0]) + SCT(*g[1]) + nice('Loaded last config!') + """Start camera""" + def start(s): + a = ga() + if not a: note('Only mapping requires you to be the host!\nYou still can load previous config though',True); return + if not getme(): note('Join the game first!',True); return + s.back(False) + RESUME() if s.main else None + with a.context: Camera() diff --git a/plugins/utilities/finder.py b/plugins/utilities/finder.py new file mode 100755 index 0000000..ce385eb --- /dev/null +++ b/plugins/utilities/finder.py @@ -0,0 +1,471 @@ +# Copyright 2025 - Solely by BrotherBoard +# Intended for personal use only +# Bug? Feedback? Telegram >> GalaxyA14user + +""" +Finder v1.0 - Find anyone + +Experimental. Feedback is appreciated. +Useful if you are looking for someone, or just messing around. + +Features: +- Fetch servers: Pings all servers, then sorts them by lowest +- Ability to cycle through x servers to collect users +- Ability to connect to servers by player name there + +Combine with Power plugin for better control. +""" + +from socket import socket, SOCK_DGRAM +from random import uniform as uf +from babase import Plugin, app +from threading import Thread +from time import time, sleep +from bauiv1 import ( + get_ip_address_type as IPT, + clipboard_set_text as COPY, + get_special_widget as zw, + containerwidget as ocw, + screenmessage as push, + buttonwidget as obw, + scrollwidget as sw, + imagewidget as iw, + textwidget as tw, + gettexture as gt, + apptimer as teck, + getsound as gs, + getmesh as gm, + Call +) +from bascenev1 import ( + disconnect_from_host as BYE, + connect_to_party as CON, + protocol_version as PT, + get_game_roster as GGR +) + +class Finder: + COL1 = (0,0.3,0.3) + COL2 = (0,0.55,0.55) + COL3 = (0,0.7,0.7) + COL4 = (0,1,1) + COL5 = (1,1,0) + MAX = 0.3 + TOP = 15 + VER = '1.0' + MEM = [] + BST = [] + SL = None + def __init__(s,src): + s.thr = [] + s.ikids = [] + s.busy = False + s.s1 = s.snd('powerup01') + c = s.__class__ + # parent + z = (460,400) + s.p = cw( + scale_origin_stack_offset=src.get_screen_space_center(), + size=z, + oac=s.bye + )[0] + # footing + sw( + parent=s.p, + size=z, + border_opacity=0 + ) + # fetch + tw( + parent=s.p, + text='Fetch Servers', + color=s.COL4, + position=(19,359) + ) + bw( + parent=s.p, + position=(360,343), + size=(80,39), + label='Fetch', + color=s.COL2, + textcolor=s.COL4, + oac=s.fresh + ) + tw( + parent=s.p, + text='Fetches, pings, and sorts public servers.', + color=s.COL3, + scale=0.8, + position=(15,330), + maxwidth=320 + ) + # separator + iw( + parent=s.p, + size=(429,1), + position=(17,330), + texture=gt('white'), + color=s.COL2 + ) + # cycle + tw( + parent=s.p, + text='Cycle Servers', + color=s.COL4, + position=(19,294) + ) + bw( + parent=s.p, + position=(360,278), + size=(80,39), + label='Cycle', + color=s.COL2, + textcolor=s.COL4, + oac=s.find + ) + tw( + parent=s.p, + text='Cycles through best servers and saves their players.', + color=s.COL3, + scale=0.8, + position=(15,265), + maxwidth=320, + v_align='center' + ) + # separator + iw( + parent=s.p, + size=(429,1), + position=(17,265), + texture=gt('white'), + color=s.COL2 + ) + # top + tw( + parent=s.p, + text='Server Cycle Limit', + color=s.COL4, + position=(19,230) + ) + s.top = tw( + parent=s.p, + position=(398,228), + size=(80,50), + text=str(c.TOP), + color=s.COL4, + editable=True, + h_align='center', + v_align='center', + corner_scale=0.1, + scale=10, + allow_clear_button=False, + shadow=0, + flatness=1, + ) + tw( + parent=s.p, + text='Maximum number of servers to cycle.', + color=s.COL3, + scale=0.8, + position=(15,201), + maxwidth=320 + ) + # separator + iw( + parent=s.p, + size=(429,1), + position=(17,200), + texture=gt('white'), + color=s.COL2 + ) + # players + pl = s.plys() + sy = max(len(pl)*30,140) + p1 = sw( + parent=s.p, + position=(20,18), + size=(205,172), + border_opacity=0.4 + ) + p2 = ocw( + parent=p1, + size=(205,sy), + background=False + ) + 0 if pl else tw( + parent=s.p, + position=(90,100), + text='Cycle some servers\nto collect players', + color=s.COL4, + maxwidth=175, + h_align='center' + ) + s.kids = [] + for _,g in enumerate(pl): + p,a = g + s.kids.append(tw( + parent=p2, + size=(200,30), + selectable=True, + click_activate=True, + color=s.COL3, + text=p, + position=(0,sy-30-30*_), + maxwidth=175, + on_activate_call=Call(s.hl,_,p), + v_align='center' + )) + # info + iw( + parent=s.p, + position=(235,18), + size=(205,172), + texture=gt('scrollWidget'), + mesh_transparent=gm('softEdgeOutside'), + opacity=0.4 + ) + s.tip = tw( + parent=s.p, + position=(310,98), + text='Select something to\nview server info', + color=s.COL4, + maxwidth=170, + h_align='center' + ) if c.SL is None else 0 + def hl(s,_,p): + [tw(t,color=s.COL3) for t in s.kids] + tw(s.kids[_],color=s.COL4) + s.info(p) + def info(s,p): + [_.delete() for _ in s.ikids] + s.ikids.clear() + s.tip and s.tip.delete() + bst = s.__class__.BST + for _ in bst: + for r in _['roster']: + if r['display_string'] == p: + i = _ + break + for _ in range(3): + t = str(i['nap'[_]]) + s.ikids.append(tw( + parent=s.p, + position=(250,155-40*_), + h_align='center', + v_align='center', + maxwidth=175, + text=t, + color=s.COL4, + size=(175,30), + selectable=True, + click_activate=True, + on_activate_call=Call(s.copy,t) + )) + s.ikids.append(bw( + parent=s.p, + position=(253,30), + size=(166,30), + label='Connect', + color=s.COL2, + textcolor=s.COL4, + oac=Call(CON,i['a'],i['p'],False) + )) + def copy(s,t): + s.ding(1,1) + TIP('Copied to clipboard!') + COPY(t) + def plys(s): + z = [] + me = app.plus.get_v1_account_name() + me = [me,'\ue063'+me] + for _ in s.__class__.BST: + a = _['a'] + if (r:=_.get('roster',{})): + for p in r: + ds = p['display_string'] + 0 if ds in me else z.append((ds,a)) + return sorted(z,key=lambda _: _[0].startswith('\ue030Server')) + def snd(s,t): + l = gs(t) + l.play() + teck(uf(0.14,0.18),l.stop) + return l + def bye(s): + s.s1.stop() + ocw(s.p,transition='out_scale') + l = s.snd('laser') + f = lambda: teck(0.01,f) if s.p else l.stop() + f() + def ding(s,i,j): + a = ['Small',''] + x,y = a[i],a[j] + s.snd('ding'+x) + teck(0.1,gs('ding'+y).play) + def fresh(s): + if s.busy: BTW("Still busy!"); return + TIP('Fetching servers...') + s.ding(1,0) + s.busy = True + p = app.plus + p.add_v1_account_transaction( + { + 'type': 'PUBLIC_PARTY_QUERY', + 'proto': PT(), + 'lang': 'English' + }, + callback=s.kang, + ) + p.run_v1_account_transactions() + def kang(s,r): + c = s.__class__ + c.MEM = r['l'] + s.thr = [] + for _ in s.__class__.MEM: + t = Thread(target=Call(s.ping,_)) + s.thr.append(t) + t.start() + teck(s.MAX*4,s.join) + def join(s): + c = s.__class__ + [t.join() for t in s.thr] + far = s.MAX*3000 + c.MEM = [_ for _ in c.MEM if _['ping']] + c.MEM.sort(key=lambda _: _['ping']) + s.thr.clear() + TIP(f'Loaded {len(c.MEM)} servers!') + s.ding(0,1) + s.busy = False + def find(s): + if s.busy: BTW("Still busy!"); return + c = s.__class__ + if not c.MEM: + BTW('Fetch some servers first!') + return + t = tw(query=s.top) + if not t.isdigit(): + BTW('Invalid cycle limit!') + return + top = int(t) + if not (0 < top < len(c.MEM)): + BTW('Cycle count is too '+['big','small'][top<=0]+'!') + return + c.TOP = top + s.ding(1,0) + TIP('Starting cycle...') + s.busy = True + s.ci = s.lr = 0 + c.BST = c.MEM[:top] + s.cycle() + def cycle(s): + _ = s.__class__.BST[s.ci] + s.ca = _['a'] + CON(s.ca,_['p'],False) + s.wait() + def wait(s,i=5): + r = GGR() + if (r != s.lr) and r: s.__class__.BST[s.ci]['roster'] = s.lr = r; return s.next() + if not i: s.__class__.BST[s.ci]['roster'] = []; return s.next() + teck(0.1,Call(s.wait,i-1)) + def next(s): + s.ci += 1 + if s.ci >= len(s.__class__.BST): + BYE() + teck(0.5,s.yay) + return + s.cycle() + def yay(s): + TIP('Cycle finished!') + s.ding(0,1) + s.busy = False + zw('squad_button').activate() + teck(0.3,byBordd.up) + def ping(s,_): + sock = ping = None + a,p = _['a'],_['p'] + sock = socket(IPT(a),SOCK_DGRAM) + try: sock.connect((a,p)) + except: ping = None + else: + st = time() + sock.settimeout(s.MAX) + yes = False + for _i in range(3): + try: + sock.send(b'\x0b') + r = sock.recv(10) + except: r = None + if r == b'\x0c': + yes = True + break + sleep(s.MAX) + ping = (time()-st)*1000 if yes else None + finally: + _['ping'] = ping + sock.close() + +# Patches +bw = lambda *,oac=None,**k: obw( + texture=gt('white'), + on_activate_call=oac, + enable_sound=False, + **k +) +cw = lambda *,size=None,oac=None,**k: (p:=ocw( + parent=zw('overlay_stack'), + background=False, + transition='in_scale', + size=size, + on_outside_click_call=oac, + **k +)) and (p,iw( + parent=p, + texture=gt('softRect'), + size=(size[0]*1.2,size[1]*1.2), + position=(-size[0]*0.1,-size[1]*0.1), + opacity=0.55, + color=(0,0,0) +),iw( + parent=p, + size=size, + texture=gt('white'), + color=Finder.COL1 +)) + +# Global +BTW = lambda t: (push(t,color=(1,1,0)),gs('block').play()) +TIP = lambda t: push(t,Finder.COL3) + +# ba_meta require api 9 +# ba_meta export babase.Plugin +class byBordd(Plugin): + BTN = None + @classmethod + def up(c): + c.BTN.activate() if c.BTN.exists() else None + def __init__(s): + from bauiv1lib import party + p = party.PartyWindow + a = '__init__' + o = getattr(p,a) + setattr(p,a,lambda z,*a,**k:(o(z,*a,**k),s.make(z))[0]) + def make(s,z): + sz = (80,30) + p = z._root_widget + x,y = (-60,z._height-45) + iw( + parent=p, + size=(sz[0]*1.34,sz[1]*1.4), + position=(x-sz[0]*0.14,y-sz[1]*0.20), + texture=gt('softRect'), + opacity=0.2, + color=(0,0,0) + ) + s.b = s.__class__.BTN = bw( + parent=p, + position=(x,y), + label='Finder', + color=Finder.COL1, + textcolor=Finder.COL3, + size=sz, + oac=lambda:Finder(s.b) + ) diff --git a/plugins/utilities/path.py b/plugins/utilities/path.py new file mode 100755 index 0000000..37e76da --- /dev/null +++ b/plugins/utilities/path.py @@ -0,0 +1,88 @@ +# Copyright 2025 - Solely by BrotherBoard +# Bug? Feedback? Telegram >> @GalaxyA14user + +""" +Path v1.0 - Where it's going to be. + +Experimental. Path tries to predict the next position of bomb. +Path relies on velocity to operate. +Optionally pass spaz node (holder) to assist prediction. +Feedback is appreciated. +""" + +from babase import Plugin +from bascenev1 import ( + timer as tick, + newnode +) + +class Path: + def __init__(s,node,holder=None): + if node.body == 'crate': return + s.node,s.kids = node,[] + s.me = holder + s.spy() + def spy(s): + n = s.node + if not n.exists(): + [_.delete() for _ in s.kids] + s.kids.clear() + return + + [_.delete() for _ in s.kids]; s.kids.clear() + + ip = n.position + iv = n.velocity + if s.me and s.me.hold_node == n: + mv = s.me.velocity + iv = (iv[0]+mv[0],iv[1]+mv[1],iv[2]+mv[2]) + + dots = 200 + ti = 1.2 + tpd = ti / dots + + tick(0.01, s.spy) + for i in range(dots): + t = i * tpd + px = ip[0] + iv[0] * t + py = ip[1] + iv[1] * t + 0.5 * -24 * t**2 + pz = ip[2] + iv[2] * t + + if py <=0: + l = newnode( + 'locator', + owner=n, + attrs={ + 'shape': 'circleOutline', + 'size': [1], + 'color': (1,1,0), + 'draw_beauty': False, + 'additive': True, + 'position':(px,py,pz) + } + ) + s.kids.append(l) + break + dot_node = newnode( + 'text', + owner=n, + attrs={ + 'text':'.', + 'scale':0.02, + 'position':(px, py, pz), + 'flatness':1, + 'in_world':True, + 'color':(1-i*4/dots,0,0), + 'shadow':0 + } + ) + s.kids.append(dot_node) + +# brobord collide grass +# ba_meta require api 9 +# ba_meta export babase.Plugin +class byBordd(Plugin): + def __init__(s): + _ = __import__('bascenev1lib').actor.bomb.Bomb + o = _.__init__ + _.__init__ = lambda z,*a,**k: (o(z,*a,**k),Path(z.node))[0] diff --git a/plugins/utilities/plugtools.py b/plugins/utilities/plugtools.py new file mode 100755 index 0000000..4ab0ed4 --- /dev/null +++ b/plugins/utilities/plugtools.py @@ -0,0 +1,475 @@ +# Copyright 2025 - Solely by BrotherBoard +# Intended for personal use only +# Bug? Feedback? Telegram >> @BroBordd + +""" +PlugTools v1.5 - Live Plugin Action + +Beta. Feedback is appreciated. +Adds a dev console tab for plugin management. + +Features vary between: +- Dynamic Control: Enables immediate loading and reloading of plugins. +- Real-time Monitoring: Reports status of plugin files (new, modified, deleted). +- Plugin Overview: Displays operational state (enabled/disabled) and integrity (original/modified). +- Plugin Data: Provides file path, size, timestamps, and code structure analysis. +- Navigation: Offers controls to browse the plugin list. +- Logging: Has a built-in log display with proper indentation. +""" + +from os.path import ( + splitext, + getmtime, + getctime, + basename, + getsize, + isfile, + exists, + join +) +from os import ( + scandir, + access, + R_OK, + stat +) +from babase import ( + PluginSpec, + Plugin, + Call, + env, + app +) +from babase._devconsole import ( + DevConsoleTabEntry as ENT, + DevConsoleTab as TAB +) +from bauiv1 import ( + get_string_width as sw, + SpecialChar as sc, + charstr as cs, + apptimer as teck, + screenmessage as push, + getsound as gs +) +from traceback import format_exc as ERR +from datetime import datetime +from importlib import reload +from typing import override +from sys import modules +from gc import collect +from ast import ( + FunctionDef, + ImportFrom, + Attribute, + ClassDef, + Import, + parse, + walk, + Name +) + +class PlugTools(TAB): + KEY = 'PT_BY' + def __init__(s): + s.bys = META() + s.bad = [] + s.logs = 'No errors' + s.mem = {_:MT(_) for _ in s.bys} + s.eye = look() + s.e = False + s.spy() + def spy(s): + b = 0 + for _ in s.bys.copy(): + if not exists(PAT(_)): + s.bys.remove(_) + push(f'Plugin {_} suddenly disappeared!\nAnd so, was removed from list.',color=(1,1,0)) + gs('block').play() + s.eye = look() + if s.hl() == _: s.hl(None) + b = 1 + sp = app.plugins.plugin_specs.get(_,0) + if not sp: continue + p = app.plugins + if getattr(sp,'enabled',False): + o = s.sp.plugin + if o in p.active_plugins: + p.active_plugins.remove(o) + if o in p.plugin_specs: + p.plugin_specs.pop(o) + del s.sp.plugin,o + collect() + try: reload(modules[NAM(_,0)]) + except: pass + continue + if MT(_) != s.mem[_] and _ not in s.bad: + s.bad.append(_) + push(f'Plugin {_} was modified!\nSee if you want to take action.',color=(1,1,0)) + gs('dingSmall').play() + b = 1 + if hasattr(s,'sp'): + e = getattr(s.sp,'enabled',False) + if e != s.e: + s.e = e + b = 1 + eye = look() + s1 = set(s.eye) + s2 = set(eye) + df = list(s2-s1) + nu = [] + if df: + for dd in df: + try: _ = kang(dd) + except: + eye.remove(dd) + continue + nu.append(_) + s.bys.append(_) + s.mem[_] = 0 + s.bad.append(_) + s.eye = eye + b = 1 + if nu: + l = len(nu) + push(f"Found {l} new plugin{['s',''][l==1]}:\n{', '.join(nu)}\nSee what to do with {['it','them'][l!=1]}",color=(1,1,0)) + gs('dingSmallHigh').play() + if b: + try: s.request_refresh() + except RuntimeError: pass + teck(0.1,s.spy) + @override + def refresh(s): + # Preload + by = s.hl() + if by not in s.bys: + by = None + s.hl(None) + s.by = by + s.sp = app.plugins.plugin_specs.get(by,0) if by else 0 + s.i = getattr(s,'i',0 if by is None else s.bys.index(by)//10) + # UI + w = s.width + x = -w/2 + z = x+w + # Bools + e = s.e = getattr(s.sp,'enabled',False) + m = by in s.bad + d = by is None + # Buttons + sx = w*0.2 + mx = sx*0.98 + z -= sx + s.button( + 'Metadata', + pos=(z,50), + size=(mx,43), + call=s.metadata, + disabled=d + ) + s.button( + ['Load','Reload'][e], + pos=(z,5), + size=(mx,43), + call=s._load, + disabled=d + ) + # Separator + s.button( + '', + pos=(z-(w*0.006),5), + size=(2,90) + ) + # Plugin info + sx = w*0.1 + z -= sx + az = z+sx/2.23 + t = 'Entry' if d else by + tw = GSW(t) + mx = sx*0.9 + s.text( + t, + pos=(az,80), + scale=1 if tw len(s.bys) + ) + # Plugins + sx = w*0.645/5 + mx = sx*0.99 + zx = mx*0.9 + z -= sx*5 + for i in range(5): + for j in range(2): + k = j*5+i+s.i*10 + if k >= len(s.bys): break + t = s.bys[k] + tw = GSW(t) + s.button( + t, + size=(mx,43), + pos=(z+sx*i,50-45*j), + label_scale=1 if tw export_line_num: + for base in node.bases: + if (isinstance(base, Name) and base.id == 'Plugin') or \ + (isinstance(base, Attribute) and base.attr == 'Plugin' and isinstance(base.value, Name) and base.value.id == 'babase'): + return f"{filename_without_ext}.{node.name}" + return None +deek = lambda: gs('deek').play() + +# brobord collide grass +# ba_meta require api 9 +# ba_meta export babase.Plugin +class byBordd(Plugin): + def __init__(s): + C = PlugTools + N = C.__name__ + E = ENT(N,C) + I = app.devconsole + I.tabs = [_ for _ in I.tabs if _.name != N]+[E] + I._tab_instances[N] = E.factory() diff --git a/plugins/utilities/power.py b/plugins/utilities/power.py new file mode 100755 index 0000000..688e87c --- /dev/null +++ b/plugins/utilities/power.py @@ -0,0 +1,793 @@ +# Copyright 2025 - Solely by BrotherBoard +# Intended for personal use only +# Bug? Feedback? Telegram >> @GalaxyA14user + +""" +Power v2.7 - With one click + +Experimental. Feedback is appreciated. +Adds a dev console tab with some features I find useful. +Power is mainly focused on the multiplayer side. +Can be considered a good tool to have around. +""" + +from datetime import datetime as DT +from typing import override +from babase import ( + clipboard_is_supported as CIS, + clipboard_set_text as CST, + Plugin, + app +) +from babase._devconsole import ( + DevConsoleTabEntry as ENT, + DevConsoleTab as TAB +) +from bascenev1 import ( + get_connection_to_host_info_2 as HOST, + disconnect_from_host as LEAVE, + disconnect_client as DISC, + broadcastmessage as push, + get_chat_messages as GCM, + connect_to_party as CON, + get_game_roster as ROST, + chatmessage as chat +) +from bauiv1 import ( + get_string_width as sw, + SpecialChar as sc, + apptimer as teck, + charstr as cs, + Call +) + +class Power(TAB): + def __init__(s): + s.j = [None,None,None]; s.ji = 1 + [setattr(s,_,None) for _ in 'cpnh'] + [setattr(s,_,{}) for _ in ['rr','hi']] + [setattr(s,_,[]) for _ in ['cm','og','r','ls']] + [setattr(s,_,0) for _ in ['ii','eii','ci','re','ri','eri','li','lii']] + teck(3,s.spy) + def rf(s): + try: s.request_refresh() + except RuntimeError: pass + def spy(s): + _ = 0 + r = ROST() + if r != s.r: + s.rr = {i['display_string']:(i['client_id'],i['players']) for i in r} + s.r = r + _ = 1 + h = HOST() + if h != s.h: + s.ri = 0 + s.h = h + _ = 1 + t = getattr(s.h, 'name', 'Not in a server') + a = getattr(s.h, 'address', '127.0.0.1') + p = getattr(s.h, 'port', '43210') + if s.h: + tt = t if t.strip() else '...' + if t.strip() or not any(key[1] == a for key in s.hi): + s.hi[(tt, a)] = (tt, p) + if tt != '...': + if ('...', a) in s.hi: + del s.hi[('...', a)] + ng = GCM() + if s.og != ng: + s.og = ng + ls = ng[-1] + ch = s.cm[0][1] if len(s.cm) else 0 + if ch and ls == s.cm[0][0]: s.cm[0] = (ls,ch+1) + else: s.cm.insert(0,(ls,1)) + if s.ci: s.ci += 1 + _ = 1 + _ and s.rf() + teck(0.1,s.spy) + @override + def refresh(s): + sf = s.width / 1605.3 + zf = s.height / 648 + x = -s.width/2 + T,B = s.text,s.button + if len(s.r) and s.ri >= len(s.r): s.ri = len(s.r) - 1 + if len(s.r) and s.eri >= len(s.r): s.eri = len(s.r) - 1 + if s.j[0] == 'JRejoin' and s.ji <= s.re: + s.ji = s.re + 1 + push('Job time cannot be less than rejoin time\nwhen job is JRejoin. Updated job time to '+str(s.ji),color=(1,1,0)) + if s.height > 100: + B( + cs(sc.UP_ARROW), + pos=(x + 10 * sf, 606*zf), + size=(280*sf,35*zf), + disabled=s.eri <= 0, + call=Call(s.mv,'eri',-1) + ) + B( + cs(sc.DOWN_ARROW), + pos=(x + 10 * sf, 290*zf), + size=(280*sf,35*zf), + disabled=s.eri >= len(s.r)-7, + call=Call(s.mv,'eri',1) + ) + nt = "No roster detected\nJoin some public party" + w = GSW(nt) + 0 if len(s.r) else T( + nt, + pos=(x + 150 * sf, 495*zf), + h_align='center', + v_align='top', + scale=1 if w<(290*sf) else (290*sf)/w + ) + for i,z in enumerate(s.rr.items()): + if i < s.eri: continue + if i>=(s.eri+7): break + n,g = z + c,p = g + w = GSW(n) + B( + n, + size=(280 * sf, 37*zf), + pos=(x + 10 * sf, (564-39*(i-s.eri))*zf), + style=[['blue','blue_bright'],['purple','purple_bright']][not p][s.c==c], + call=Call(s.prv,c,p,n), + label_scale=1 if w < 280 * sf else (280 * sf)/w + ) + B( + '', + size=(280 * sf, 2), + pos=(x + 10 * sf, 280*zf), + style='bright' + ) + bb = s.c is None + B( + 'Bomb' if bb else (['Client','Host'][s.c==-1]+f' {s.c}'), + pos=(x + 10 * sf, 230*zf), + size=(280 * sf, 40*zf), + disabled=bb, + call=Call(push,str(s.n)) + ) + B( + 'Mention', + size=(280 * sf, 40*zf), + pos=(x + 10 * sf, 185*zf), + call=Call(chat,str(s.n)), + disabled=bb + ) + B( + 'Players', + size=(280 * sf, 40*zf), + pos=(x + 10 * sf, 140*zf), + call=Call(push,'\n'.join([' '.join([f'{i}={j}' for i,j in _.items()]) for _ in s.p]) if s.p else ''), + disabled=bb or (not s.p) + ) + B( + 'Kick', + size=(280 * sf, 40*zf), + pos=(x + 10 * sf, 95*zf), + call=Call(KICK,lambda:s.rr[s.n][0]), + disabled=bb or (s.c==-1) + ) + B( + 'JKick', + size=(280 * sf, 40*zf), + pos=(x + 10 * sf, 50*zf), + call=Call(s.job,Call(KICK,lambda:s.rr[s.n][0]),['JKick',s.c,s.n]), + disabled=bb or (s.c==-1) + ) + B( + 'Vote', + size=(280 * sf, 40*zf), + pos=(x + 10 * sf, 5*zf), + call=Call(chat,'1'), + disabled=not s.r + ) + B( + '', + size=(2, 635*zf), + pos=(x + 300 * sf, 5*zf), + style='bright' + ) + t = getattr(s.h,'name','Not in a server') + a = getattr(s.h,'address','127.0.0.1') + p = getattr(s.h,'port','43210') + w = GSW(t) + B( + t if t.strip() else 'Loading...', + size=(400 * sf, 35*zf), + pos=(x + 311 * sf, 606*zf), + disabled=not s.h, + label_scale=1 if w < 390 * sf else (390 * sf)/w, + call=Call(push,f"{t}\nHosted on build {getattr(s.h,'build_number','0')}" if t.strip() else 'Server is still loading...\nIf it remains stuck on this\nthen either party is full, or a network issue.'), + ) + w = GSW(a) + B( + a, + size=(300 * sf, 35*zf), + pos=(x + 311 * sf, 568*zf), + call=Call(COPY,a), + disabled=not s.h, + label_scale=1 if w < 290 * sf else (290 * sf)/w + ) + w = GSW(str(p)) + B( + str(p), + size=(97 * sf, 35*zf), + pos=(x + 614 * sf, 568*zf), + disabled=not s.h, + call=Call(COPY,str(p)), + label_scale=1 if w < 90 * sf else (90 * sf)/w + ) + B( + 'Leave', + size=(400 * sf, 35*zf), + pos=(x + 311 * sf, 530*zf), + call=LEAVE, + disabled=not s.h + ) + B( + 'Rejoin', + size=(200 * sf, 35*zf), + pos=(x + 311 * sf, 492*zf), + call=Call(REJOIN,a,p,lambda:s.re), + disabled=not s.h + ) + B( + 'JRejoin', + size=(197 * sf, 35*zf), + pos=(x + 514 * sf, 492*zf), + call=Call(s.job,Call(REJOIN,a,p,lambda:s.re),['JRejoin',a,str(p)]), + disabled=not s.h + ) + B( + '+', + size=(131 * sf, 35*zf), + pos=(x + 579 * sf, 454*zf), + call=Call(s.mv,'re',1) + ) + B( + str(s.re or 0.1), + size=(131 * sf, 35*zf), + pos=(x + 444 * sf, 454*zf), + call=Call(push,f"Rejoins after {s.re or 0.1} second{['','s'][s.re!=1]}\nKeep this 0.1 unless server kicks fast rejoins\nLife in server = job time - rejoin time") + ) + B( + '-', + size=(131 * sf, 35*zf), + pos=(x + 311 * sf, 454*zf), + disabled=s.re<=0.5, + call=Call(s.mv,'re',-1) + ) + B( + '', + size=(2, 635*zf), + pos=(x + 720 * sf, 5*zf), + style='bright' + ) + B( + '', + size=(400 * sf, 2), + pos=(x + 311 * sf, 445*zf), + style='bright' + ) + for i,e in enumerate(s.hi.items()): + if i < s.eii: continue + if i >= (s.eii+9): break + g,v = e + _,a = g + n,p = v + w = GSW(n) + B( + n, + size=(400 * sf, 37*zf), + pos=(x + 311 * sf, (358-39*(i-s.eii))*zf), + label_scale=1 if w < 290 * sf else (290 * sf)/w, + call=Call(JOIN,a,p,False), + disabled=n == '...' + ) + nt = "Server join history\nServers you join are saved here" + w = GSW(nt) + 0 if len(s.hi) else T( + nt, + pos=(x + 510 * sf, 265*zf), + v_align='top', + scale=1 if w<(380*sf) else (380*sf)/w + ) + B( + cs(sc.DOWN_ARROW), + pos=(x + 311 * sf, 8*zf), + size=(398*sf, 35*zf), + disabled=s.eii >= len(s.hi)-9, + call=Call(s.mv,'eii',1) + ) + B( + cs(sc.UP_ARROW), + pos=(x + 311 * sf, 400*zf), + size=(400*sf, 35*zf), + disabled=s.eii <= 0, + call=Call(s.mv,'eii',-1) + ) + bb = s.j[0] is None + B( + 'No job' if bb else 'Job', + size=(300 * sf, 35*zf), + pos=(x + 727 * sf, 606*zf), + call=Call(push,s.j[0]), + disabled=bb + ) + w = 0 if bb else GSW(str(s.j[1])) + B( + 'Target' if bb else str(s.j[1]), + size=(300 * sf, 35*zf), + pos=(x + 727 * sf, 568*zf), + call=Call(push,s.j[2]), + disabled=bb, + label_scale=1 if w<110 * sf else (110 * sf)/w + ) + B( + 'Stop', + size=(300 * sf, 35*zf), + pos=(x + 727 * sf, 530*zf), + call=Call(s.job,None,[None,None,None]), + disabled=bb + ) + B( + '+', + size=(96 * sf, 35*zf), + pos=(x + 931 * sf, 492*zf), + call=Call(s.mv,'ji',1) + ) + B( + str(s.ji or 0.1), + size=(100 * sf, 35*zf), + pos=(x + 828 * sf, 492*zf), + call=Call(push,f"Job runs every {s.ji or 0.1} second{['','s'][s.ji!=1]}") + ) + B( + '-', + size=(98 * sf, 35*zf), + pos=(x + 727 * sf, 492*zf), + disabled=s.ji<=0.5, + call=Call(s.mv,'ji',-1) + ) + B( + 'Power', + size=(300 * sf, 35*zf), + pos=(x + 727 * sf, 454*zf), + call=Call(push,'Power v2.5 FullUI\nCollapse dev console to switch to MinUI') + ) + B( + '', + size=(300 * sf, 2), + pos=(x + 727 * sf, 445*zf), + style='bright' + ) + B( + '', + size=(2, 635*zf), + pos=(x + 1034 * sf, 5*zf), + style='bright' + ) + 0 if len(s.cm) else T( + 'Chat is still empty.\nHurry up and fill it with nonesense', + pos=(x+1320 * sf, 330 * zf) + ) + for i,g in enumerate(s.cm): + if i < s.ci: continue + if i >= s.ci+15: break + i = i - s.ci + m,_ = g + sn,ms = m.split(': ',1) + w = GSW(sn) + w = [w,30*sf][w<30*sf] + s1 = [w,200*sf][w>200*sf] + B( + sn, + size=(s1,35*zf), + pos=(x + 1040*sf, (48+37*i)*zf), + style='purple', + label_scale=1 if w<(s1-10*sf) else (s1-10*sf)/w, + call=Call(s.chk,sn) + ) + s2 = 555*sf - s1 - 53*(_>1) + B( + '', + size=(s2,35*zf), + pos=(x + 1045*sf+s1, (48+37*i)*zf), + style='black' + ) + w = GSW(ms) + T( + ms, + pos=(x + s1+(1050)*sf, (48+17+37*i)*zf), + scale=1 if w<(s2-10*sf) else (s2-10*sf)/w, + h_align='left' + ) + z = f'x{_}' + w = GSW(z) + _>1 and B( + z, + pos=(x+s1+s2+(1050)*sf,(48+37*i)*zf), + size=(50*sf,35*zf), + label_scale=1 if w<(40*sf) else (40*sf)/w, + style='yellow_bright' + ) + B( + cs(sc.DOWN_ARROW), + pos=(x+1042*sf,8*zf), + size=(555*sf,35*zf), + call=Call(s.mv,'ci',-1), + disabled=s.ci <= 0 or not s.cm + ) + B( + cs(sc.UP_ARROW), + pos=(x+1042*sf,606*zf), + size=(555*sf,35*zf), + call=Call(s.mv,'ci',1), + disabled=(s.ci >= len(s.cm)-15) or not s.cm + ) + B( + cs(sc.DOWN_ARROW), + pos=(x+727*sf,8*zf), + size=(300*sf,35*zf), + disabled=(s.li >= len(s.ls)-16) or not s.ls, + call=Call(s.mv,'li',1) + ) + B( + cs(sc.UP_ARROW), + pos=(x+727*sf,400*zf), + size=(300*sf,35*zf), + disabled=s.li<=0, + call=Call(s.mv,'li',-1) + ) + 0 if s.ls else T( + 'Job logs here\nLike you even care', + pos=(x+875*sf,232*zf) + ) + for _,g in enumerate(s.ls): + if _ < s.li: continue + if _ >= s.li+16: break + _ = _ - s.li + l,t = g + B( + '', + pos=(x+727*sf,(376-_*22)*zf), + size=(300*sf,20*zf), + label_scale=0.7, + corner_radius=0, + style='black', + call=Call(push,t) + ) + T( + l, + pos=(x+732*sf,(386-_*22)*zf), + scale=0.6, + h_align='left' + ) + else: + B( + cs(sc.DOWN_ARROW), + pos=(x + 10 * sf, 10), + size=(30 * sf, s.height-17), + disabled=(s.ri >= len(s.r)-3) or not s.r, + call=Call(s.mv,'ri',1) + ) + B( + cs(sc.UP_ARROW), + pos=(x + 250 * sf, 10), + size=(30 * sf, s.height-17), + disabled=(s.ri <= 0) or not s.r, + call=Call(s.mv,'ri',-1) + ) + nt = "No roster\nYou're alone" + w = GSW(nt) + 0 if len(s.r) else T( + nt, + pos=(x + 147 * sf, s.height-17), + h_align='center', + v_align='top', + scale=1 if w<(200*sf) else (200*sf)/w + ) + for i,z in enumerate(s.rr.items()): + if i < s.ri: continue + if i>=(s.ri+3): break + n,g = z + c,p = g + w = GSW(n) + B( + n, + size=(210 * sf, 27), + pos=(x + 40 * sf, s.height-35-27*(i-s.ri)), + style=[['blue','blue_bright'],['purple','purple_bright']][not p][s.c==c], + call=Call(s.prv,c,p,n), + label_scale=1 if w < 200 * sf else (200 * sf)/w + ) + bb = s.c is None + B( + 'Bomb' if bb else (['Client','Host'][s.c==-1]+f' {s.c}'), + pos=(x + 287 * sf, s.height-34), + size=(120 * sf, 27), + disabled=bb, + call=Call(push,str(s.n)) + ) + B( + 'Mention', + size=(120 * sf, 27), + pos=(x + 287 * sf, s.height-90), + call=Call(chat,str(s.n)), + disabled=bb + ) + B( + 'Players', + size=(120 * sf, 27), + pos=(x + 287 * sf, s.height-62), + call=Call(push,'\n'.join([' '.join([f'{i}={j}' for i,j in _.items()]) for _ in s.p]) if s.p else ''), + disabled=bb or (not s.p) + ) + B( + 'Kick', + size=(120 * sf, 27), + pos=(x + 407 * sf, s.height-34), + call=Call(KICK,lambda:s.rr[s.n][0]), + disabled=bb or (s.c==-1) + ) + B( + 'JKick', + size=(120 * sf, 27), + pos=(x + 407 * sf, s.height-62), + call=Call(s.job,Call(KICK,lambda:s.rr[s.n][0]),['JKick',s.c,s.n]), + disabled=bb or (s.c==-1) + ) + B( + 'Vote', + size=(120 * sf, 27), + pos=(x + 407 * sf, s.height-90), + call=Call(chat,'1'), + disabled=not s.r + ) + B( + '', + size=(2, s.height-17), + pos=(x + 535 * sf, 10), + style='bright' + ) + bb = s.j[0] is None + B( + 'No job' if bb else 'Job', + size=(120 * sf, 27), + pos=(x + 544 * sf, s.height-34), + call=Call(push,s.j[0]), + disabled=bb + ) + w = 0 if bb else GSW(str(s.j[1])) + B( + 'Target' if bb else str(s.j[1]), + size=(120 * sf, 27), + pos=(x + 544 * sf, s.height-62), + call=Call(push,s.j[2]), + disabled=bb, + label_scale=1 if w<110 * sf else (110 * sf)/w + ) + B( + 'Stop', + size=(120 * sf, 27), + pos=(x + 544 * sf, s.height-90), + call=Call(s.job,None,[None,None,None]), + disabled=bb + ) + B( + '+', + size=(50 * sf, 27), + pos=(x + 664 * sf, s.height-34), + call=Call(s.mv,'ji',1) + ) + B( + str(s.ji or 0.1), + size=(50 * sf, 27), + pos=(x + 664 * sf, s.height-62), + call=Call(push,f"Job runs every {s.ji or 0.1} second{['','s'][s.ji!=1]}") + ) + B( + '-', + size=(50 * sf, 27), + pos=(x + 664 * sf, s.height-90), + disabled=s.ji<=0.5, + call=Call(s.mv,'ji',-1) + ) + B( + '', + size=(2, s.height-17), + pos=(x + 722 * sf, 10), + style='bright' + ) + t = getattr(s.h,'name','Not in a server') + a = getattr(s.h,'address','127.0.0.1') + p = getattr(s.h,'port','43210') + w = GSW(t) + B( + t if t.strip() else 'Loading...', + size=(300 * sf, 27), + pos=(x + 732 * sf, s.height-34), + disabled=not s.h, + label_scale=1 if w < 290 * sf else (290 * sf)/w, + call=Call(push,f"{t}\nHosted on build {getattr(s.h,'build_number','0')}" if t.strip() else 'Server is still loading...\nIf it remains stuck on this\nthen either party is full, or a network issue.'), + ) + w = GSW(a) + B( + a, + size=(200 * sf, 27), + pos=(x + 732 * sf, s.height-62), + call=Call(COPY,a), + disabled=not s.h, + label_scale=1 if w < 190 * sf else (190 * sf)/w + ) + w = GSW(str(p)) + B( + str(p), + size=(97 * sf, 27), + pos=(x + 935 * sf, s.height-62), + disabled=not s.h, + call=Call(COPY,str(p)), + label_scale=1 if w < 90 * sf else (90 * sf)/w + ) + B( + 'Leave', + size=(100 * sf, 27), + pos=(x + 732 * sf, s.height-90), + call=LEAVE, + disabled=not s.h + ) + B( + 'Rejoin', + size=(97 * sf, 27), + pos=(x + 835 * sf, s.height-90), + call=Call(REJOIN,a,p,lambda:s.re), + disabled=not s.h + ) + B( + 'JRejoin', + size=(97 * sf, 27), + pos=(x + 935 * sf, s.height-90), + call=Call(s.job,Call(REJOIN,a,p,lambda:s.re),['JRejoin',a,str(p)]), + disabled=not s.h + ) + B( + '+', + size=(50 * sf, 27), + pos=(x + 1035 * sf, s.height-34), + call=Call(s.mv,'re',1) + ) + B( + str(s.re or 0.1), + size=(50 * sf, 27), + pos=(x + 1035 * sf, s.height-62), + call=Call(push,f"Rejoins after {s.re or 0.1} second{['','s'][s.re!=1]}\nKeep this 0.1 unless server kicks fast rejoins\nLife in server = job time - rejoin time") + ) + B( + '-', + size=(50 * sf, 27), + pos=(x + 1035 * sf, s.height-90), + disabled=s.re<=0.5, + call=Call(s.mv,'re',-1) + ) + B( + '', + size=(2, s.height-17), + pos=(x + 1092 * sf, 10), + style='bright' + ) + for i,e in enumerate(s.hi.items()): + if i < s.ii: continue + if i >= (s.ii+3): break + g,v = e + _,a = g + n,p = v + w = GSW(n) + B( + n, + size=(300 * sf, 27), + pos=(x + 1134 * sf, s.height-34-28*(i-s.ii)), + label_scale=1 if w < 290 * sf else (290 * sf)/w, + call=Call(JOIN,a,p,False), + disabled=n == '...' + ) + nt = "Your server join history\nwill appear here. Hi." + w = GSW(nt) + 0 if len(s.hi) else T( + nt, + pos=(x + 1285 * sf, s.height-17), + h_align='center', + v_align='top', + scale=1 if w<(280*sf) else (280*sf)/w + ) + B( + cs(sc.DOWN_ARROW), + pos=(x + 1102 * sf, 10), + size=(30 * sf, s.height-17), + disabled=s.ii >= len(s.hi)-3, + call=Call(s.mv,'ii',1) + ) + B( + cs(sc.UP_ARROW), + pos=(x + 1436 * sf, 10), + size=(30 * sf, s.height-17), + disabled=s.ii <= 0, + call=Call(s.mv,'ii',-1) + ) + B( + 'Force leave', + call=FORCE, + pos=(x + 1469 * sf, s.height-34), + size=(130 * sf, 27), + label_scale=0.9 + ) + B( + 'Laugh', + call=Call(chat,'hahaha'), + pos=(x + 1469 * sf, s.height-62), + size=(130 * sf, 27) + ) + B( + 'Power', + call=Call(push,'Power v2.5 MinUI\nExpand dev console to switch to FullUI. thanks.'), + pos=(x + 1469 * sf, s.height-90), + size=(130 * sf, 27) + ) + def log(s,t): + s.ls.append((t,NOW())) + if s.lii < 99: + s.lii += 1 + if s.li == s.lii-17: s.li += 1 + else: s.ls.pop(0) + s.rf() + def mv(s,a,i): + setattr(s,a,getattr(s,a)+i) + s.rf() + def job(s,f,j): + s.j = j + s.lf = f + s.hd = j[1] if s.j[0] == 'JRejoin' else j[2] + if f is not None: + s._job(f) + push('Job started',color=(1,1,0)) + else: push('Job stopped',color=(1,1,0)) + s.rf() + def _job(s,f): + if f != s.lf: return + s.log(f'[{s.lii:02}] [{s.j[0]}] {s.hd}') + f(); teck(s.ji or 0.1,Call(s._job,f)) + def prv(s,c,p,n): + s.c,s.p,s.n = c,p,n + s.rf() + def chk(s,pn): + y = 0 + for n,g in s.rr.items(): + c,p = g + if n == pn: y = 1 + else: + for _ in p: + if pn in [_['name'],_['name_full']]: y = 1 + if y: s.prv(c,p,n); break + +HAS = app.ui_v1.has_main_window +SAVE = app.classic.save_ui_state +KICK = lambda f: DISC(f()) +FORCE = lambda: teck(0.7 if HAS() else 0.1,lambda: 0 if HAS() else app.classic.return_to_main_menu_session_gracefully()) +JOIN = lambda *a: (SAVE() or 1) and CON(*a) +GSW = lambda s: sw(s,suppress_warning=True) +REJOIN = lambda a,p,f: ((LEAVE() if getattr(HOST(),'name','') else 0) or 1) and teck(f() or 0.1,Call(JOIN,a,p,False)) +COPY = lambda s: ((CST(s) or 1) if CIS() else push('Clipboard not supported!')) and push('Copied!',color=(0,1,0)) +NOW = lambda: DT.now().strftime("%H:%M:%S") + +# brobord collide grass +# ba_meta require api 9 +# ba_meta export babase.Plugin +class byBordd(Plugin): + def __init__(s): + C = Power + N = C.__name__ + E = ENT(N,C) + I = app.devconsole + I.tabs = [_ for _ in I.tabs if _.name != N]+[E] + I._tab_instances[N] = E.factory() diff --git a/plugins/utilities/replay.py b/plugins/utilities/replay.py new file mode 100755 index 0000000..d34fd03 --- /dev/null +++ b/plugins/utilities/replay.py @@ -0,0 +1,1251 @@ +# Copyright 2025 - Solely by BrotherBoard +# Intended for personal use only +# Bug? Feedback? Telegram >> @BroBordd + +""" +Replay v2.5 - Simple replay player + +Experimental. Feedback is appreciated. +Adds a button to pause menu and watch menu. + +Features: +- Common features (pause/play/seek/speed/replay) +- Press on progress bar to seek anywhere +- Advanced free camera target control +- Ability to zoom in/out to target +- Uses pybrp to display how long a replay is +- Good UI with detailed toast pop ups +- Ability to show/hide UI +- Uses threading everywhere +""" + +from babase import Plugin +from bauiv1 import ( + get_virtual_screen_size as res, + get_special_widget as gsw, + clipboard_set_text as COPY, + get_replays_dir as rdir, + containerwidget as ocw, + screenmessage as push, + spinnerwidget as spin, + buttonwidget as obw, + fade_screen as fade, + scrollwidget as sw, + SpecialChar as sc, + imagewidget as iw, + textwidget as otw, + gettexture as gt, + apptimer as teck, + AppTimer as tock, + getsound as gs, + UIScale as UIS, + charstr as cs, + Call, + app +) +from bascenev1 import ( + set_replay_speed_exponent as SET, + get_replay_speed_exponent as GET, + new_replay_session as PLAY, + resume_replay as RESUME, + pause_replay as PAUSE, + seek_replay as SEEK, + is_in_replay as ON +) +from _babase import ( + set_camera_position as SCP, + get_camera_position as GCP, + set_camera_manual as SCM, + set_camera_target as SCT, + get_camera_target as GCT +) +from os.path import join, dirname, getsize, basename +from time import time, strftime, gmtime +from random import uniform as uf +from threading import Thread +from os import listdir as ls +from struct import unpack + +class Replay: + VER = '2.5' + COL1 = (0.18,0.18,0.18) + COL2 = (1,1,1) + COL3 = (0,1,0) + COL4 = (0,1,1) + BUSY = False + @classmethod + def BUS(c,b=None): + if b is None: return c.BUSY + c.BUSY = b + def __init__(s,source=None): + s.sl = s.rn = s.buf = None + s.ohno = False + s._h = _H() + s.p = s.cw( + src=source.get_screen_space_center(), + p=GOS(), + size=(400,500), + oac=lambda:(ocw(s.p,transition='out_scale' if source and source.exists() else 'out_left'),s.snd('laser'),s.trs.stop()) + ) + s.trs = s.snd('powerup01') + s.tw( + p=s.p, + h_align='center', + text='Replay', + pos=(175,460), + scale=2 + ) + sy = 360 + p1 = sw( + parent=s.p, + size=(sy,sy), + position=(25,80) + ) + s.rd = rdir() + a = [_ for _ in ls(s.rd) if _.endswith('.brp')] + v = 30*len(a) + p2 = ocw( + parent=p1, + background=False, + size=(sy,v) + ) + s.kids = [] + for i,_ in enumerate(a): + t = s.tw( + p=p2, + click_activate=True, + selectable=True, + pos=(0,v-30*i-30), + text=_, + maxwidth=sy, + size=(sy,30), + color=s.COL2, + oac=Call(s.hl,i,_) + ) + s.kids.append(t) + s.psrc = None + for _ in range(3): + b = s.bw( + p=s.p, + pos=(25+120*_,30), + size=(120,40), + label=['Show','Copy','Run'][_], + oac=Call(s.con,[s.show,s.copy,s.play][_]), + icon=gt(['folder','file','nextLevelIcon'][_]) + ) + if _ == 2: s.psrc = b + def snd(s,t): + h = gs(t) + h.play() + teck(uf(0.14,0.17),h.stop) + return h + def get(s): + return join(s.rd,s.rn) + def copy(s): + s.snd('dingSmallHigh') + COPY(s.get()) + push('Copied replay path to clipboard!',color=s.COL3) + def show(s): + gs('ding').play() + push(s.get(),color=s.COL3) + def con(s,f): + if s.sl is None: BTW('Select a replay!'); return + if ON(): BTW('A replay is already running!'); return + return f() + def hl(s,i,n): + if s.sl == i: + s.psrc = s.kids[i] + s.play() + return + s.sl = i + s.rn = n + [otw(_,color=s.COL2) for _ in s.kids] + otw(s.kids[i],color=s.COL3) + def play(s): + if s.BUS(): return + s.BUS(True) + gs('deek').play() + s.load() + def load(s): + src = s.psrc.get_screen_space_center() + if s.psrc.get_widget_type() == 'text': + src = (src[0]-170,src[1]) + s.parc = c = s.cw( + src=src, + size=(300,200), + p=GOS() + ) + s.tw( + p=c, + text='Player', + pos=(125,150), + h_align='center', + scale=1.4 + ) + spin( + parent=c, + size=60, + position=(75,100) + ) + s.st = s.tw( + p=c, + text='Reading...', + pos=(115,87) + ) + s.tpar = s.tw( + p=c, + pos=(125,30), + maxwidth=240, + text=f'{s.rn} with total of {getsize(s.get())} bytes\nstreaming bytes to pybrp_stream', + h_align='center' + ) + s.tpar2 = s.tw( + p=c, + maxwidth=240, + pos=(30,20), + v_align='bottom' + ) + s.par = [0,1] + teck(0.5,Thread(target=s.calc).start) + teck(0.5,s.fpar) + s.spy(s.calc2) + def fpar(s): + a,b = s.par + teck(0.1,s.fpar) if (a!=b) and (not s.ohno) else 0 + if not a: return + p = a/b*100 + t = '\u2588'*int(p)+'\u2591'*int(100-p) + if not s.ohno: + try: + otw(s.tpar,text=t) + otw(s.tpar2,text=f'{a} of {b} bytes read') + except: return + def calc(s): + try: s.buf = GMS(s._h,s.get(),s.par) + except: s.buf = 0 + def calc2(s,t): + otw(s.st,text='Starting...' if t else 'Wait what?') + otw(s.tpar2,text=f'result was {t} milleseconds') if t else t + if not t: + s.ohno = True + otw(s.tpar,text='pybrp returned zero duration, error?\nclosing this window in 5 seconds') + otw(s.tpar2,text='') + teck(1 if t else 5,Call(s._play,t)) + def spy(s,f,i=60): + if not i: + s.buf = None + f(None) + return + if s.buf is not None: + b = s.buf + s.buf = None + f(b) + return + teck(0.5,Call(s.spy,f,i-1)) + def _play(s,t): + if t == 0: + BTW("Couldn't load replay!") + ocw(s.parc,transition='out_scale') + s.BUS(False) + return + SET(0) + fade(1) + Player(path=s.get(),duration=t) + s.BUS(False) + bw = lambda s,p=None,oac=None,pos=None,**k: obw( + parent=p, + color=s.COL1, + textcolor=s.COL2, + on_activate_call=oac, + position=pos, + button_type='square', + enable_sound=False, + **k + ) + cw = lambda s,p=None,pos=None,src=None,oac=None,**k: ocw( + color=s.COL1, + parent=p, + position=pos, + scale_origin_stack_offset=src, + transition='in_scale', + on_outside_click_call=oac, + **k + ) + tw = lambda s,color=None,oac=None,p=None,pos=None,**k: otw( + parent=p, + position=pos, + color=color or s.COL2, + on_activate_call=oac, + **k + ) + +class Player: + TICK = 0.01 + COL0 = (0.5,0,0) + COL1 = (1,0,0) + COL2 = (0.5,0.5,0) + COL3 = (1,1,0) + COL4 = (0,0.5,0) + COL5 = (0,1,0) + COL6 = (0,0.5,0.5) + COL7 = (0,1,1) + COL8 = (0.6,0.6,0.6) + COL9 = (8,0,0) + COL10 = (0.5,0.25,0) + COL11 = (1,0.5,0) + COL12 = (0.5,0.25,0.5) + COL13 = (1,0.5,1) + COL14 = (0.5,0.5,0.5) + COL15 = (1,1,1) + COL16 = (0.1, 0.2, 0.4) + COL17 = (1, 1.7, 2) + def __init__(s,path,duration): + s.path = path + s.du = duration + s.ds = s.du / 1000 + s.ps = s.nah = s.camon = s.snma = s.gay = False + s.caml = None + s.rn = s.st = s.pr = 0 + s.camz = 1 + [setattr(s,_,[]) for _ in ['kids','camkids','hdkids','snkids','snuikids']] + PLAY(path) + x,y = res() + s.sy = 80 + s.p = ocw( + size=(x,s.sy), + stack_offset=(0,-y/2+s.sy/2), + background=False + ) + s.bg = iw( + parent=s.p, + texture=gt('black'), + size=(x+3,s.sy+5), + position=(0,-2), + opacity=0.4 + ) + s.mkui() + s.mkhd() + # finally + s.sp = 1 + s.foc() + s.play() + def mkhd(s): + f = s.hdkids.append + s.tex=['\u25bc','\u25b2'] + s.kekb = s.bw( + p=s.p, + pos=(20,15), + size=(50,50), + oac=s.kek, + color=s.COL10 + ) + f(s.kekb) + s.kekt = otw( + parent=s.p, + text=s.tex[s.nah], + position=(44,30), + scale=2, + shadow=0.4, + color=s.COL11 + ) + f(s.kekt) + f(iw( + parent=s.p, + position=(18,13), + size=(54,54), + color=s.COL10, + texture=gt('white'), + opacity=0.4 + )) + def killhd(s): + [_.delete() for _ in s.hdkids] + s.hdkids.clear() + def mkui(s): + s.up = True + f = s.kids.append + x,y = res() + sy = s.sy + p = s.p + # exit + f(s.bw( + p=p, + pos=(x-65,15), + size=(50,50), + color=s.COL0, + oac=s.bye + )) + c = s.COL1 + f(iw( + parent=p, + texture=gt('crossOut'), + color=(c[0]*10,c[1]*10,c[2]*10), + position=(x-60,20), + size=(40,40) + )) + # speed + for _ in range(2): + a = [ + 'FAST_FORWARD_BUTTON', + 'REWIND_BUTTON' + ][_] + pos = (x-130-260*_,15) + f(s.bw( + p=p, + pos=pos, + size=(50,50), + color=s.COL2, + oac=Call(s.boost,[1,-1][_]), + repeat=True + )) + f(otw( + parent=p, + text=cs(getattr(sc,a)), + color=s.COL3, + position=(pos[0]-2,pos[1]+13), + h_align='center', + v_align='center', + scale=1.8, + shadow=0.3 + )) + # seek + for _ in range(2): + a = [ + 'RIGHT_ARROW', + 'LEFT_ARROW' + ][_] + pos = (x-195-130*_,15) + f(s.bw( + p=p, + pos=pos, + size=(50,50), + color=s.COL4, + oac=Call(s.seek,[1,-1][_]), + repeat=True + )) + f(otw( + parent=p, + text=cs(getattr(sc,a)), + color=s.COL5, + position=(pos[0]-1,pos[1]+12), + h_align='center', + v_align='center', + scale=1.7, + shadow=0.2 + )) + # pause + pos = (x-260,15) + f(s.bw( + p=p, + pos=pos, + size=(50,50), + color=s.COL6, + oac=s.toggle + )) + s.tt = otw( + parent=p, + color=s.COL7, + position=(pos[0]+12,pos[1]+11), + scale=1.5, + shadow=0.3 + ) + f(s.tt) + s.toggle(dry=True) + # replay + pos = (x-455,15) + f(s.bw( + p=p, + pos=pos, + size=(50,50), + color=s.COL12, + oac=s.rloop + )) + c = s.COL13 + sk = 1.5 + f(iw( + parent=p, + texture=gt('replayIcon'), + color=(c[0]*sk,c[1]*sk,c[2]*sk), + position=(pos[0]+2,pos[1]+1), + size=(47,47), + )) + # progress + pos = (285,sy/2-2) + s.px = x-790 + f(iw( + parent=p, + texture=gt('white'), + size=(s.px,5), + position=pos, + opacity=0.4, + color=s.COL8 + )) + s.nbp = (pos[0]-24,pos[1]-22) + s.nb = iw( + parent=p, + texture=gt('nub'), + size=(50,50), + position=s.nbp, + opacity=0.4, + color=s.COL9 + ) + f(s.nb) + # timestamp + s.ct = otw( + parent=p, + position=(155,40), + color=s.COL7, + text=FOR(s.rn-s.st) + ) + f(s.ct) + f(otw( + parent=p, + position=(155,11), + text=FOR(s.ds), + color=s.COL6 + )) + # sensor + sx,sy = (285,15) + n = 100 + tp = s.px/n + for _ in range(n): + f(obw( + label='', + parent=p, + position=(sx+tp*_,sy), + size=(tp,50), + texture=gt('empty'), + enable_sound=False, + on_activate_call=Call(s.jump,_/n), + selectable=False + )) + # camera + f(s.bw( + p=s.p, + pos=(85,15), + size=(50,50), + color=s.COL14, + oac=s.cam + )) + c = s.COL15 + sk = 1.5 + f(iw( + parent=s.p, + texture=gt('achievementOutline'), + position=(88,18), + color=(c[0]*sk,c[1]*sk,c[2]*sk), + size=(45,45) + )) + # info + ix,iy = (443,98) + s.ok = iw( + texture=gt('white'), + position=(x-456,100), + parent=p, + size=(ix,iy), + opacity=0 + ) + f(s.ok) + s.ok2 = otw( + position=(x-ix+182.5,iy+64), + h_align='center', + scale=1.2, + parent=p, + maxwidth=ix-20 + ) + f(s.ok2) + s.ok3 = otw( + position=(x-ix+182.5,iy+10), + h_align='center', + parent=p, + maxwidth=ix-20 + ) + f(s.ok3) + def mkcamui(s): + f = s.camkids.append + x,y = (19,100) + s.cambg = iw( + parent=s.p, + size=(235,260), + position=(x,y), + texture=gt('white'), + color=s.COL14, + opacity=0.05 + ) + f(s.cambg) + # tip + s.cambg2 = iw( + parent=s.p, + size=(235,100), + opacity=0.05, + position=(x,y+267), + color=s.COL14, + texture=gt('white') + ) + f(s.cambg2) + s.fcam() + f(otw( + parent=s.p, + h_align='center', + v_align='center', + text='To maintain animated\nsmooth camera, keep\nzoom at auto.', + maxwidth=205, + max_height=190, + position=(x+92,y+300), + color=s.COL15 + )) + # reset + f(s.bw( + p=s.p, + label='Reset', + color=s.COL14, + textcolor=s.COL15, + size=(205,30), + pos=(x+15,y+7), + oac=s.camr + )) + # seperator + f(iw( + parent=s.p, + size=(219,2), + position=(x+8,y+44), + texture=gt('white'), + color=s.COL15, + opacity=0.6 + )) + # look + f(s.bw( + p=s.p, + label='Look', + pos=(x+15,y+53), + color=s.COL14, + textcolor=s.COL15, + size=(205,30), + oac=s.look + )) + s.ltw = otw( + parent=s.p, + text=str(RND(s.caml) if s.caml else 'players'), + v_align='center', + h_align='center', + position=(x+92,y+90), + color=s.COL14, + maxwidth=205, + max_height=40 + ) + f(s.ltw) + f(otw( + parent=s.p, + text='Currently looking at:', + v_align='center', + h_align='center', + position=(x+92,y+120), + color=s.COL15, + maxwidth=205, + max_height=40 + )) + # seperator + f(iw( + parent=s.p, + size=(219,2), + position=(x+8,y+154), + texture=gt('white'), + color=s.COL15, + opacity=0.6 + )) + # zoom + [f(s.bw( + p=s.p, + label=['-','+'][_], + pos=(x+13+113*_,y+163), + color=s.COL14, + textcolor=s.COL15, + size=(98,30), + repeat=True, + oac=Call(s.zoom,[1,-1][_]) + )) for _ in [0,1]] + s.ztw = otw( + parent=s.p, + text=f'x{round(0.5**(s.camz-1),2)}' if s.camz != 1 else 'x1.0' if s.gay else 'auto', + v_align='center', + h_align='center', + position=(x+92,y+200), + color=s.COL14, + maxwidth=205, + max_height=40 + ) + f(s.ztw) + f(otw( + parent=s.p, + text='Current zoom:', + v_align='center', + h_align='center', + position=(x+92,y+227), + color=s.COL15, + maxwidth=205, + max_height=40 + )) + def zoom(s,i): + n = round(s.camz+i*0.05,2) + if s.camz == 1 and not s.gay: + SCM(True) + s.camp = GCP() + s.caml = GCT() + otw(s.ltw,text=str(RND(s.caml))) + if n == 1 and not s.gay: + SCM(False) + s.caml = None + otw(s.ltw,text='players') + s.camz = n + otw(s.ztw,text=f'x{round(0.5**(n-1),2)}' if n != 1 else 'x1.0' if s.gay else 'auto') + s.zom() + def look(s): + s.killui() + s.killhd() + s.fkek(0.4,-0.1) + s.camlo = s.caml + s.campo = GCP() + s.gayo = s.gay + s.mksns() + s.mksnui() + s.mksnb() + s.mksni() + def _look(s,x,y): + o = s.caml or GCT() + sk = 0.7*s.camz + s.caml = n = (o[0]+x*sk,o[1]+y*sk,o[2]) + 0 if s.snma else otw(s.snt,text=str(RND(n))) + s.camp = GCP() + if s.camz != 1: s.gay = True + s.camz = 1 + def foc(s): + s.tfoc = tock(0.01,s.focus,repeat=True) + def focus(s): + SCT(*s.caml) if s.caml else 0 + def zom(s): + if s.camz == 1 and not s.gay: return + z = s.camz + tx,ty,tz = GCT() + px,py,pz = s.camp + npx = tx+(px-tx)*z + npy = ty+(py-ty)*z + npz = tz+(pz-tz)*z + SCP(npx,npy,npz) + def fockill(s): + s.tfoc = None + def snsave(s): + s.snbye() + def snbye(s): + s.killsn() + s.mkui() + s.mkhd() + s.fkek(0,0.1) + s.pro() + def mksns(s): + x,y = res() + sz = 50 + a = int(x/sz) + b = int(y/sz) + ha = a/2 + hb = b/2 + [s.snkids.append(obw( + parent=s.p, + size=(sz,sz), + position=(i*sz,j*sz), + texture=gt('empty'), + enable_sound=False, + on_activate_call=Call(s._look,i-ha,j-hb), + label='', + repeat=True + )) + for i in range(a) + for j in range(b)] + def mksnui(s): + f = s.snuikids.append + f(iw( + parent=s.p, + position=(0,3), + color=s.COL14, + opacity=0.4, + texture=gt('white'), + size=(232,190) + )) + # buttons + f(s.bw( + p=s.p, + pos=(14,50), + size=(204,30), + label='Target Players', + color=s.COL14, + textcolor=s.COL15, + oac=s.sntar + )) + f(s.bw( + p=s.p, + pos=(10,90), + color=s.COL14, + label='Cancel', + size=(99,30), + textcolor=s.COL15, + oac=s.sncancel + )) + f(s.bw( + p=s.p, + pos=(123,90), + color=s.COL14, + label='Save', + size=(99,30), + textcolor=s.COL15, + oac=s.snsave + )) + # info + f(otw( + parent=s.p, + position=(90,160), + color=s.COL15, + text='Currently looking at:', + h_align='center', + maxwidth=220 + )) + s.snt = otw( + parent=s.p, + position=(90,130), + color=s.COL14, + h_align='center', + text=str(RND(s.caml) if s.caml else 'players') + ) + f(s.snt) + # tip + f(iw( + parent=s.p, + position=(0,200), + color=s.COL14, + opacity=0.4, + texture=gt('white'), + size=(232,110) + )) + f(otw( + parent=s.p, + position=(90,240), + text='Longpress anywhere\nto look around. Tap on \nsomething to look at it.\nPause for calmer control!', + h_align='center', + v_align='center', + maxwidth=225, + max_height=105 + )) + # crosshair + x,y = res() + h = 20 + [f(iw( + parent=s.p, + position=(x/2,y/2-h/2+h*0.1) if _ else (x/2-h/2,y/2+h*0.1), + size=(3,h*1.15) if _ else (h*1.15,3), + color=s.COL1, + texture=gt('white') + )) for _ in [0,1]] + # top + k = 60 + for j in range(2): + f(iw( + parent=s.p, + texture=gt('white'), + color=s.COL1, + position=(x/2+[-k,k-h][j],y/2+k), + size=(h*1.1,3) + )) + # right + for j in range(2): + f(iw( + parent=s.p, + texture=gt('white'), + color=s.COL1, + position=(x/2+k,y/2+[k-h,-k+h*0.3][j]), + size=(3,h*+1.1) + )) + # bottom + for j in range(2): + f(iw( + parent=s.p, + texture=gt('white'), + color=s.COL1, + position=(x/2+[-k,k-h][j],y/2-h/2-k+h*0.8), + size=(h*1.1,3) + )) + # left + for j in range(2): + f(iw( + parent=s.p, + texture=gt('white'), + color=s.COL1, + position=(x/2-k,y/2+[k-h,-k+h*0.3][j]), + size=(3,h*1.1) + )) + def killsnui(s): + [_.delete() for _ in s.snuikids] + def snhide(s): + if getattr(s,'snbusy',0): return + s.snma = not s.snma + s.snbusy = True + if s.snma: + s.snanim(204,14,-1) + obw(s.snbtn,label=cs(sc.UP_ARROW)) + s.killsnui() + else: + obw(s.snbtn,texture=gt('white')) + s.snanim(36,7,1) + iw(s.sni,opacity=0) + def mksni(s): + s.sni = iw( + parent=s.p, + position=(7,8), + color=s.COL14, + opacity=0, + size=(36,33), + texture=gt('white') + ) + s.snkids.append(s.sni) + def mksnb(s): + s.snbtn = s.bw( + p=s.p, + pos=(14,10), + color=s.COL14, + label='Cinema Mode', + size=(204,30), + textcolor=s.COL15, + oac=s.snhide + ) + s.snkids.append(s.snbtn) + def snanim(s,a,b,i): + a += (163/35)*i + b += 0.2*i + obw(s.snbtn,size=(a,30),position=(b,10)) + if not (14>=b>=7): + s.snbusy = False + if s.snma: + obw(s.snbtn,texture=gt('empty')) + iw(s.sni,opacity=0.4) + else: + s.mksnui() + s.snbtn.delete() + s.mksnb() + obw(s.snbtn,label='Cinema Mode') + return + teck(0.004,Call(s.snanim,a,b,i)) + def killsn(s): + s.killsnui() + [_.delete() for _ in s.snkids] + def all(s): + return s.trash()+s.hdkids+s.snkids + def sntar(s): + s.caml = None + otw(s.snt,text='players') + if s.camz != 1 or s.gay: + s.camz = 1 + s.gay = False + SCM(False) + def sncancel(s): + s.caml = s.camlo + s.camp = s.campo + s.gay = s.gayo + if s.camz != 1 or s.gay: + SCM(True) + SCP(*s.camp) + s.snbye() + def cam(s): + if s.camon: + s.camon = False + [_.delete() for _ in s.camkids] + s.camkids.clear() + s.fcam(0.4,-0.1) + else: + s.camon = True + s.mkcamui() + def camr(s): + SCM(False) + s.caml = None + s.gay = False + s.camz = 1 + otw(s.ltw,text='players') + otw(s.ztw,text='auto') + def fcam(s,i=0,a=0.1): + if i > 0.4 or i < 0: + if a < 0: s.cambg.delete() + return + if not s.cambg.exists(): return + iw(s.cambg,opacity=i) + iw(s.cambg2,opacity=i) + teck(0.02,Call(s.fcam,i+a,a)) + def rloop(s): + s.loop() + s.fixps() + s.hm('Replay',f'Version {Replay.VER} BETA',s.COL12,s.COL13) + def killui(s): + s.up = s.camon = False + [_.delete() for _ in s.trash()] + s.kids.clear() + s.camkids.clear() + def trash(s): + return s.kids+s.camkids + def kek(s): + if getattr(s,'kekbusy',0): return + s.kekbusy = True + if getattr(s,'tbye',0) and getattr(s,'frbro',0): + s.frbro = s.tbye = False + s.okt = None + s.nah = b = not s.nah + otw(s.kekt,text=s.tex[b]) + if b: + teck(0.2,lambda:obw(s.kekb,texture=gt('empty'))) + s.fkek(0.4,-0.05) + s.killui() + else: + obw(s.kekb,texture=gt('white')) + s.fkek(0,0.05) + s.mkui() + s.pro() + teck(0.21,Call(setattr,s,'kekbusy',0)) + def fkek(s,i=0,a=0.1): + if i > 0.4 or i < 0: return + if not s.bg.exists(): return + iw(s.bg,opacity=i) + teck(0.02,Call(s.fkek,i+a,a)) + def hm(s,t1,t2,c1,c2): + if getattr(s,'tbye',0) and getattr(s,'frbro',0): + s.frbro = s.tbye = False + s.okt = None + iw(s.ok,color=c1) + otw(s.ok2,text=t1,color=c2) + otw(s.ok3,text=t2,color=c2) + s.fok() + s.okt = tock(1.5,s.unhm) + def unhm(s): + s.fok(0.7,-0.1) + [otw(_,text='') for _ in [s.ok2,s.ok3] if _.exists()] + def fok(s,i=0,a=0.1): + if i > 0.7 or i < 0: return + if not s.ok.exists(): return + iw(s.ok,opacity=i) + teck(0.02,Call(s.fok,i+a,a)) + def toggle(s,dry=False,shut=False): + if not dry: s.ps = not s.ps + t = cs(getattr(sc,['PAUSE','PLAY'][s.ps]+'_BUTTON')) + otw(s.tt,text=t) + if not dry: + if not shut: s.hm(['Resume','Pause'][s.ps],basename(s.path)+f' of {getsize(s.path)} bytes',s.COL6,s.COL7) + if s.ps: + s.stop() + PAUSE() + else: + s.play() + RESUME() + def fixps(s): + if not s.ps: return + s.toggle(shut=True) + teck(0.02,Call(s.toggle,shut=True)) + def clock(s): + t = time() + r = t - s.rt + s.rt = t + s.rn += r * s.sp + def boost(s,i): + n = GET()+i + SET(n) + s.sp = 2**n + h = 'Snail Mode' if s.sp == 0.0625 else 'Slow Motion' if s.sp<1 else 'Quake Pro' if s.sp==16 else 'Fast Motion' if s.sp>1 else 'Normal Speed' + s.hm(h,f'Current exponent: x{s.sp}',s.COL2,s.COL3) + def play(s): + s.rt = time() + s.clock() + s.ptplay() + s.clt = tock(s.TICK,s.clock,repeat=True) + def stop(s): + s.clt = None + s.ptkill() + def ptkill(s): + s.pt = None + def ptplay(s): + s.pt = tock(s.TICK,s.pro,repeat=True) + def seek(s,i): + h = ['Forward by','Rewind by'][i==-1] + i = i * s.sp + i = (s.ds/20)*i + t = (s.rn-s.st)+i + if (t >= s.ds) or (t <= 0): + s.loop() + else: + s.st = s.rn-t + s.replay() + SEEK(t) + s.rt = time() + s.fixps() + i = abs(round(i,2)) + s.hm('Seek',h+f" {i} second{['s',''][i==1]}",s.COL4,s.COL5) + def jump(s,p): + t = s.ds * p + s.st = s.rn-t + s.replay() + SEEK(t) + s.rt = time() + s.fixps() + def bye(s): + if getattr(s,'frbro',0): s._bye(); return + s.hm('Exit','Press again to confirm',s.COL0,s.COL1) + s.frbro = True + s.tbye = tock(1.5,Call(setattr,s,'frbro',False)) + def _bye(s): + fade(0,time=0.75,endcall=Call(fade,1,time=0.75)) + gs('deek').play() + BYE() + s.stop() + s.fockill() + s.tbye = None + SCM(False) + def pro(s): + t = s.rn-s.st + if s.rn-s.st >= s.ds: s.loop() + x,y = s.nbp + p = (t/s.ds)*s.px + try: + iw(s.nb,position=(x+p,y)) + otw(s.ct,text=FOR(t)) + except ReferenceError: pass + def replay(s): + SEEK(-10**10) + def loop(s): + s.st = s.rn = 0 + s.replay() + bw = lambda s,label='',p=None,oac=None,pos=None,texture='white',**k: obw( + parent=p, + on_activate_call=oac, + position=pos, + label=label, + texture=gt(texture), + enable_sound=False, + **k + ) + +# Tools +BYE = lambda: app.classic.return_to_main_menu_session_gracefully(reset_ui=False) +BTW = lambda t: (gs('block').play() or 1) and push(t,color=(1,1,0)) +GOS = lambda: gsw('overlay_stack') +FOR = lambda t: strftime('%H:%M:%S',gmtime(t)) +SCL = lambda a,b,c=None: ((s:=app.ui_v1.uiscale), a if s is UIS.SMALL else b if s is UIS.MEDIUM else (c or b))[1] +RND = lambda t: type(t)([round(_,1) for _ in t]) + +# pybrp +Z = lambda _:[0]*_ +G_FREQS = lambda:[ + 101342,9667,3497,1072,0,3793,*Z(2),2815,5235,*Z(3),3570,*Z(3), + 1383,*Z(3),2970,*Z(2),2857,*Z(8),1199,*Z(30), + 1494,1974,*Z(12),1351,*Z(122),1475,*Z(65) +] +CMD_NAMES=lambda:{0:'BaseTimeStep',1:'StepSceneGraph',2:'AddSceneGraph',3:'RemoveSceneGraph',4:'AddNode',5:'NodeOnCreate',6:'SetForegroundScene',7:'RemoveNode',8:'AddMaterial',9:'RemoveMaterial',10:'AddMaterialComponent',11:'AddTexture',12:'RemoveTexture',13:'AddMesh',14:'RemoveMesh',15:'AddSound',16:'RemoveSound',17:'AddCollisionMesh',18:'RemoveCollisionMesh',19:'ConnectNodeAttribute',20:'NodeMessage',21:'SetNodeAttrFloat',22:'SetNodeAttrInt32',23:'SetNodeAttrBool',24:'SetNodeAttrFloats',25:'SetNodeAttrInt32s',26:'SetNodeAttrString',27:'SetNodeAttrNode',28:'SetNodeAttrNodeNull',29:'SetNodeAttrNodes',30:'SetNodeAttrPlayer',31:'SetNodeAttrPlayerNull',32:'SetNodeAttrMaterials',33:'SetNodeAttrTexture',34:'SetNodeAttrTextureNull',35:'SetNodeAttrTextures',36:'SetNodeAttrSound',37:'SetNodeAttrSoundNull',38:'SetNodeAttrSounds',39:'SetNodeAttrMesh',40:'SetNodeAttrMeshNull',41:'SetNodeAttrMeshes',42:'SetNodeAttrCollisionMesh',43:'SetNodeAttrCollisionMeshNull',44:'SetNodeAttrCollisionMeshes',45:'PlaySoundAtPosition',46:'PlaySound',47:'EmitBGDynamics',48:'EndOfFile',49:'DynamicsCorrection',50:'ScreenMessageBottom',51:'ScreenMessageTop',52:'AddData',53:'RemoveData',54:'CameraShake'} +class _H: + class _N: + def __init__(self): + self.l,self.r,self.p,self.f=-1,-1,0,0 + def __init__(self): + gf,self.nodes=G_FREQS(),[self._N()for _ in range(511)] + for i in range(256):self.nodes[i].f=gf[i] + nc=256 + while nc<511: + s1,s2=-1,-1 + i=0 + while self.nodes[i].p!=0:i+=1 + s1=i;i+=1 + while self.nodes[i].p!=0:i+=1 + s2=i;i+=1 + while iself.nodes[s2].f: + if self.nodes[i].f>7 + if not comp:return src + out,ptr,l=bytearray(),src[1:],len(src) + bl=((l-1)*8)-rem;bit=0 + while bit>3]>>(bit&7))&1;bit+=1 + if m_bit: + n=510 + while n>=256: + if bit>=bl:raise ValueError("Incomplete Huffman code") + p_bit=(ptr[bit>>3]>>(bit&7))&1;bit+=1 + n=self.nodes[n].l if p_bit==0 else self.nodes[n].r + out.append(n) + else: + if bit+8>bl:break + bi,b_in_b=bit>>3,bit&7 + val=ptr[bi]if b_in_b==0 else(ptr[bi]>>b_in_b)|(ptr[bi+1]<<(8-b_in_b)) + out.append(val&255);bit+=8 + return bytes(out) +def GMS(_h, brp_path, par): + total_ms = 0 + with open(brp_path, 'rb') as f: + f.seek(0,2) + par[1] = f.tell() + f.seek(6) + while True: + if par: par[0] = f.tell() + b_data = f.read(1) + if not b_data: + break + b1, comp_len = b_data[0], 0 + if b1 < 254: + comp_len = b1 + elif b1 == 254: + comp_len = int.from_bytes(f.read(2), 'little') + else: # 255 + comp_len = int.from_bytes(f.read(4), 'little') + if comp_len == 0: + continue + raw_msg = _h.decompress(f.read(comp_len)) + if not raw_msg or raw_msg[0] != 1: + continue + sub_off = 1 + while sub_off < len(raw_msg): + try: + sub_size = int.from_bytes(raw_msg[sub_off:sub_off+2], 'little') + except IndexError: + break + except ValueError: + break + sub_data = raw_msg[sub_off+2:sub_off+2+sub_size] + if sub_data and sub_data[0] == 0: + total_ms += sub_data[1] + sub_off += 2 + sub_size + if par: par[0] = par[1] + return total_ms + +# brobord collide grass +# ba_meta require api 9 +# ba_meta export babase.Plugin +class byBordd(Plugin): + def __init__(s): + from bauiv1lib.ingamemenu import InGameMenuWindow as m + a = '_refresh_in_game'; o = getattr(m,a) + setattr(m,a,lambda v,*a,**k:(s.mk(v),o(v,*a,**k))[1]) + from bauiv1lib.watch import WatchWindow as n + b = '__init__'; p = getattr(n,b) + setattr(n,b,lambda v,*a,**k:(p(v,*a,**k),s.mk(v,1))[0]) + def mk(s,v,i=0): + if i: + x = v._width/2+SCL(v._scroll_width*-0.5+93,0)+100 + y = v.yoffs-SCL(63,10)-25 + s.b = Replay.bw( + Replay, + p=v._root_widget, + label='Replay', + pos=(x,y) if i else (-70,0), + icon=gt('replayIcon'), + iconscale=1.6 if i else 0.8, + size=(140,50) if i else (90,35), + oac=lambda:Replay(source=s.b) + ) + diff --git a/plugins/utilities/topmsg.py b/plugins/utilities/topmsg.py old mode 100644 new mode 100755 index b27749b..f5bcae0 --- a/plugins/utilities/topmsg.py +++ b/plugins/utilities/topmsg.py @@ -1,26 +1,31 @@ -from babase import app, Plugin as p -from bascenev1 import gettexture as x, apptimer as z -from bascenev1 import broadcastmessage as push, get_foreground_host_activity as ga, get_chat_messages as gcm +# Copyright 2025 - Solely by BrotherBoard +# Intended for personal use only +# Bug? Feedback? Telegram >> @BroBordd + +""" +TopMsg v1.1.2 - Chat top right + +When chat is muted, shows chat messages top right. +Prevents spam and flooding screen. +Does not repeat messages. +""" + +from babase import app, Plugin +from bascenev1 import ( + get_chat_messages as gcm, + broadcastmessage as push, + apptimer as z +) # ba_meta require api 9 - # ba_meta export babase.Plugin - - -class byBordd(p): +class byBordd(Plugin): + __init__ = lambda s: (setattr(s,'la',None),z(5,s.ear))[1] def ear(s): a = gcm() if a and s.la != a[-1]: if app.config.resolve('Chat Muted'): - push(a[-1], (1, 1, 1), True, s.con) + push(a[-1],(1,1,1),True) s.la = a[-1] z(0.1, s.ear) - def get(s): - with ga().context: - s.con = x("upButton") - s.la = None - s.ear() - - -z(1.0, byBordd().get) diff --git a/plugins/utilities/updown.py b/plugins/utilities/updown.py index 08b3f3c..2c38cb8 100644 --- a/plugins/utilities/updown.py +++ b/plugins/utilities/updown.py @@ -38,7 +38,6 @@ class VeryPW(party.PartyWindow): def _d(s): s._p(1) def _p(s, i=0): - print(s._chat_texts) s._w1 = gcm() if s._f: s._o = tw(query=s._text_field)