diff --git a/concept/pluginfile.txt b/concept/pluginfile.txt new file mode 100644 index 0000000..cfa0923 --- /dev/null +++ b/concept/pluginfile.txt @@ -0,0 +1,28 @@ +permission ids: +1 - GUI access +2 - API access +3 - game obj access + +structure: +{ +"name": "pluginname", +"permissions": [ + 1, + 2, + 3, + ], +"API": """ +def main(): + return {"plugin": "Enabled"} +""", +"init": """ +import tkinter as tk +def main(): + addmenu = tk.Menu(menu) + menu.add_cascade(label="exampleplugin", menu=addmenu) + addmenu.add_command(label="NULL", command=print()) +""", +} + + + diff --git a/errors.log b/errors.log new file mode 100644 index 0000000..e69de29 diff --git a/main.py b/main.py index aa65e0e..799f09e 100644 --- a/main.py +++ b/main.py @@ -1,6 +1,11 @@ import sys # Justus Jan Nico Wolff sys.dont_write_bytecode = True +stderrfile = open("errors.log", 'w') +stderrfile.write("") +stderrfile.close() +stderrfile = open("errors.log", 'a') +#sys.stderr = stderrfile import subprocess import pkg_resources import ast @@ -61,11 +66,15 @@ import easygui import base64 import simpleaudio as sa import multiprocessing +import threading global gamedata global cooldown global version +global plugindir version = "HE2.2-Hashengine V2.2" +#plugindir = "plugins" +plugindir = "testplugins" cooldown = False gamedata = {} @@ -679,18 +688,24 @@ def testing(): global testproc global running global clog + global APIPLUG try: if running == True: return except: pass running = True - testproc = multiprocessing.Process(target=execgame, args=(prepspecified(gamedata), clog.get())) + testproc = multiprocessing.Process(target=execgame, args=(prepspecified(gamedata), clog.get(), APIPLUG)) testproc.start() def APIGEN(): + global APIPLUG API = {"print": log, "HASHBASE": hashengine} API["HASHGAME"] = maingame API["SOUND"] = lambda data: gsound(data, maingame) + for i in APIPLUG: + exec(i, globals()) + temp = globals()["PLUGINAPIFUNC"]() + API.update(temp) return API def run(): @@ -808,6 +823,7 @@ def build(): shutil.copyfile(i, target+"/"+i) shutil.copyfile(__file__, target+"/"+"player.py") shutil.copyfile("requirements.txt", target+"/"+"requirements.txt") + shutil.copytree(plugindir, target+"/"+plugindir) file = open(target+"/main.py", 'w') file.write(""" import player @@ -815,7 +831,7 @@ import ast file = open("game.HEGF", 'r') file = file.read() file = ast.literal_eval(file) -player.execgame(file) +player.execgame(file, fAPIPLUG=player.loadplugins(False)) """) print("done.") print("building finished!") @@ -929,6 +945,19 @@ def ungridobjtrees(): objtree.grid_remove() atritree.grid_remove() +def loadplugins(GUIe=True): + global APIPLUG + for pluginname in os.listdir(plugindir): + file = open(plugindir+"/"+pluginname, 'r') + plugindata = file.read() + plugindata = ast.literal_eval(plugindata) + file.close() + if "init" in plugindata and GUIe == True: + exec(plugindata["init"], globals()) + if "API" in plugindata: + APIPLUG.append(plugindata["API"]) + return APIPLUG + def GUIinit(): global container global objtree @@ -938,6 +967,13 @@ def GUIinit(): global GUIe global clog global models + global menu + global filemenu + global addmenu + global testmenu + global buildmenu + global settings + global langmenu models = [] GUIe = True container = tk.Tk() @@ -1025,7 +1061,7 @@ def GUIinit(): menu.add_cascade(label=LH.string("langs"), menu=langmenu) for i in LH.getlangs(): langmenu.add_command(label=i, command=lambda i=i: selectlang(i)) - + loadplugins() container.mainloop() # attribute changers @@ -1134,11 +1170,13 @@ def aposy(old): return old return tempvar -def execgame(gametree, shouldlog=True): +def execgame(gametree, shouldlog=True, fAPIPLUG=[]): global GUIe global preview global clog global models + global APIPLUG + APIPLUG = fAPIPLUG preview = hashengine.game(renderer=nullrend, sounddir="/") GUIe = False models = [] @@ -1146,6 +1184,16 @@ def execgame(gametree, shouldlog=True): clog = shouldlog run() +def cmd(): + print(f"hashengine version: {version}") + print("CMD") + while True: + cmd = input("> ") + try: + exec(cmd, globals()) + except Exception as e: + print(e) + class rsound: def __init__(self): self.spath = "" @@ -1165,6 +1213,8 @@ global crucial global DCTE global gamexsize global gameysize +global APIPLUG +APIPLUG = [] gamexsize = 10 gameysize = 10 crucial = ["obj"] @@ -1206,4 +1256,6 @@ attypes = { "color3": hashengine.color3, } if __name__ == "__main__": - GUIinit() \ No newline at end of file + threading.Thread(target=cmd).start() + GUIinit() +stderrfile.close() \ No newline at end of file diff --git a/testbuilds/plugin/out/errors.log b/testbuilds/plugin/out/errors.log new file mode 100644 index 0000000..e69de29 diff --git a/testbuilds/plugin/out/game.HEGF b/testbuilds/plugin/out/game.HEGF new file mode 100644 index 0000000..5751f16 --- /dev/null +++ b/testbuilds/plugin/out/game.HEGF @@ -0,0 +1 @@ +[[{'id': 'script', 'name': 'Skript', 'SID': 'pESBBUtxhoOcWqInhNTdqqqxonEoVbvhJQjHsuSCvsKorTpsPXvWbfElhVTeFmwSZPkFdgBQIoeHNfiBmqXjnRzeIIyokRTJtXXeSyIkxyWqYDOnwNWnVFtWXWoLPJkfcxLFKNJvdClNbgEcZNWJOqgPbTfunPEFeXzUyFYZadEeCIndNkYhWNjaZkIhhEKvdgVCjrLjmpbosAvOeSlhxEyTfJnrBgJgMSMeJOSPQRuMQMwZgjyCSldZtPJuiXA', 'args': {'code': 'from tkinter import messagebox\nmessagebox.showinfo(plugin, plugin)'}}], {}, 10, 10] \ No newline at end of file diff --git a/testbuilds/plugin/out/hashengine.py b/testbuilds/plugin/out/hashengine.py new file mode 100644 index 0000000..c9d6d86 --- /dev/null +++ b/testbuilds/plugin/out/hashengine.py @@ -0,0 +1,391 @@ +import tkinter as tk +import string +import random +import threading +import time +import wave +import os +import math + +class stdrend: + def __init__(self, size, cam): + self._size = size + self._grid = {} + self._win = tk.Tk() + + tkeys = list(string.ascii_letters) + self._keys = {} + for i in tkeys: + self._keys[i] = False + self._win.bind("", self.keypupd) + self._win.bind("", self.keydupd) + + for y in range(size[1]): + for x in range(size[0]): + temp = tk.Label(text=" ") + temp.grid(row=y, column=x) + self._win.update() + self._grid[f"{x}:{y}"] = temp + + def keypupd(self, event): + event = event.char + if event in self._keys: + self._keys[event] = True + + def keydupd(self, event): + event = event.char + if event in self._keys: + self._keys[event] = False + + def getkeys(self): + return self._keys + + def coltohex(self, target): + colors = [] + target = [target.r, target.g, target.b] + for i in target: + colors.append(("0"*(2-len(hex(i)[2:])))+hex(i)[2:]) + out = "" + for i in colors: + out = out + i + return "#"+out + + def update(self): + self._win.update() + + def pix(self, x, y, text, bcolor, fcolor): + if f"{x}:{y}" in self._grid: + self._grid[f"{x}:{y}"].config(text=text, bg=self.coltohex(bcolor), fg=self.coltohex(fcolor)) + +class color3: + def __init__(self, r=0, g=0, b=0): + self.r = r + self.g = g + self.b = b + self._type = "color3" + + def __add__(self, v): + temp = color3(self.r+v.r, self.g+v.g, self.b+v.b) + temp.r = temp.r%255 + temp.g = temp.g%255 + temp.b = temp.b%255 + return temp + + def __sub__(self, v): + temp = color3(self.r-v.r, self.g-v.g, self.b-v.b) + temp.r = temp.r%255 + temp.g = temp.g%255 + temp.b = temp.b%255 + return temp + + def __mul__(self, v): + temp = color3(self.r*v.r, self.g*v.g, self.b*v.b) + temp.r = temp.r%255 + temp.g = temp.g%255 + temp.b = temp.b%255 + return temp + + def __iadd__(self, v): + temp = color3(self.r+v.r, self.g+v.g, self.b+v.b) + temp.r = temp.r%255 + temp.g = temp.g%255 + temp.b = temp.b%255 + return temp + + def __isub__(self, v): + temp = color3(self.r-v.r, self.g-v.g, self.b-v.b) + temp.r = temp.r%255 + temp.g = temp.g%255 + temp.b = temp.b%255 + return temp + + def __imul__(self, v): + temp = color3(self.r*v.r, self.g*v.g, self.b*v.b) + temp.r = temp.r%255 + temp.g = temp.g%255 + temp.b = temp.b%255 + return temp + +class vector2: + def __init__(self, x=0, y=0): + self.x = x + self.y = y + self._type = "vector2" + + def _magnitude(self): + return abs(self.x+self.y) + + def __add__(self, v): + return vector2(self.x+v.x, self.y+v.y) + + def __sub__(self, v): + return vector2(self.x-v.x, self.y-v.y) + + def __mul__(self, v): + return vector2(self.x*v.x, self.y*v.y) + + def __iadd__(self, v): + return vector2(self.x+v.x, self.y+v.y) + + def __isub__(self, v): + return vector2(self.x-v.x, self.y-v.y) + + def __imul__(self, v): + return vector2(self.x*v.x, self.y*v.y) + +class NULL: + def __init__(self): + return None + +class enum: + def __init__(self, sel): + self._sel = dict(sel) + for i in self._sel: + setattr(self, i, self._sel[i]) + + def getposssel(self): + return list(self._sel.keys()) + +def loadsound(path): + with wave.open(path) as fd: + frames = fd.readframes(1000000000) + return frames + +cammode = enum({"editable": 0, "follow": 1}) + +class event: + def __init__(self): + self._attached = [] + + def execute(self): + threads = [] + for i in self._attached: + temp = threading.Thread(target=i) + temp.start() + threads.append(temp) + return threads + + def attach(self, target): + self._attached.append(target) + +class obj: + def __init__(self): + self.position = vector2() + self.char = " " + self.ID = 0 + self.gravity = 0 + self.acceleration = vector2() + self.velocity = vector2() + self.friction = 0 + self.collide = True + self.anchored = False + self.bcolor = color3(255, 255, 255) + self.fcolor = color3() + self._touching = event() + +class model: + def __init__(self, objects): + self._objects = objects + self.ID = 0 + +class camera(obj): + def __init__(self): + super().__init__() + self.mode = cammode.editable + self.subject = None + self.collide = False + self.touch = False + self.char = " " + + def update(self): + if self.mode == cammode.follow and self.subject: + self.position = self.subject.position + +class seqobj: + def __init__(self, objects): + self._objects = objects + + def moveby(self, pos): + for i in self._objects: + i.position += pos + +class game: + def __init__(self, size=[10, 10], renderer=stdrend, sounddir=""): + if renderer == None: raise TypeError("Renderer class needed!") + self.sounds = {} + self.currentsounds = [] + for i in os.listdir(sounddir): + if not "." in i: continue + if i.split(".")[1] != "wav": continue + self.sounds[i.split(".")[0]] = loadsound(sounddir+"/"+i) + self._size = size + self._objects = {} + self._SIDS = {} + self._SEQSIDS = {} + self.camera = camera() + self._renderer = renderer(size, self.camera) + self._threads = [] + + def getobjbySID(self, target): + return self._objects[self._SIDS[target]] + + def getobjseqbySID(self, target): + out = [] + for i in self._SEQSIDS[target]: + out.append(self._objects[i]) + return seqobj(out) + + def isdown(self, key): + temp = self._renderer.getkeys() + if key in temp: + return temp[key] + + def collidingpos(self, pos, ignore): + out = [] + for i in self._objects: + i = self._objects[i] + if i.ID in ignore: continue + if math.dist([i.position.x, i.position.y], [pos.x, pos.y]) < 1 and i.collide == True: + out.append(i) + return out + + def colliding(self, target): + out = [] + if target.collide == False: return [] + out = self.collidingpos(target.position, [target.ID,]) + return out + + def handlecollision(self, target: obj, target2: obj): + if target2.anchored == True: + target.velocity = vector2() + else: + xtrue = False + ytrue = False + if target.velocity.x > 0: + xtrue = True + if target.velocity.y > 0: + ytrue = True + half = vector2(abs(target.velocity.x)/2, abs(target.velocity.y)/2) + if not xtrue: + half = vector2(-abs(half.x), half.y) + if not ytrue: + half = vector2(half.x, -abs(half.y)) + target.velocity = vector2(half.x, half.y) + target2.velocity = half + self._threads.extend(target._touching.execute()) + self._threads.extend(target2._touching.execute()) + + def calcphysobj(self, target: obj): + opos = vector2(target.position.x, target.position.y) + collide = False + if target.anchored == True: return [opos, collide] + if target.collide == True: + colliding = self.collidingpos(target.position+target.velocity, [target.ID,]) + for i in colliding: + target._touching.execute() + i._touching.execute() + self.handlecollision(target, i) + collide = True + target.position += target.velocity + target.velocity += vector2(0, target.gravity) + target.velocity += target.acceleration + temp = 2 + if target.friction != 0: + temp = 2 / target.friction + else: + temp = 1 + x = target.velocity.x + y = target.velocity.y + if x != 0: + x = x/temp + if y != 0: + y = y/temp + target.velocity = vector2(x, y) + return [opos, collide] + + def calcphysmodel(self, target: model): + for i in target._objects: + self.calcphysobj(i) + + def addobj(self, obj): + id = "" + for i in range(256): + id = id + random.choice(list(string.ascii_letters)) + obj.ID = id + self._objects[id] = obj + + def removeobj(self, obj): + self._objects.pop(obj.ID) + obj.ID = NULL() + + def removeobjbyid(self, id): + self._objects.pop(id).ID = NULL() + + def between(self, min, max, target): + if min < target < max: return True + return False + + def getobjbyid(self, id): + return self._objects[id] + + def render(self): + tochange = [] + for x in range(self._size[0]): + for y in range(self._size[1]): + tochange.append((x, y)) + #self._renderer.pix(x, y, " ", color3(255, 255, 255), color3(255, 255, 255)) + for i in list(self._objects.values()): + if isinstance(i, obj): + pos = i.position + self.camera.position + if not self.between(-1, self._size[0], pos.x) or not self.between(-1, self._size[1], pos.y): continue# + pos = vector2(round(pos.x), round(pos.y)) + self._renderer.pix(pos.x, pos.y, i.char, i.bcolor, i.fcolor) + if (pos.x, pos.y) in tochange: tochange.remove((pos.x, pos.y)) + if isinstance(i, model): + for tobj in i._objects: + pos = tobj.position + self.camera.position + if not self.between(-1, self._size[0], pos.x) or not self.between(-1, self._size[1], pos.y): continue# + pos = vector2(round(pos.x), round(pos.y)) + self._renderer.pix(pos.x, pos.y, tobj.char, tobj.bcolor, tobj.fcolor) + if (pos.x, pos.y) in tochange: tochange.remove((pos.x, pos.y)) + for i in tochange: + self._renderer.pix(i[0], i[1], " ", color3(255, 255, 255), color3(255, 255, 255)) + self._renderer.update() + + def startscript(self, target): + temp = threading.Thread(target=target) + temp.start() + self._threads.append(temp) + + def stopscripts(self): + for i in self._threads: + i.join(.0) + + def stopsounds(self): + for i in self.currentsounds: + i.stop() + +if __name__ == "__main__": + testgame = game(sounddir="testsound") + object = obj() + object.char = "#" + object.anchored = False + object.position = vector2(5, 5) + object.gravity = 1 + floor = obj() + floor.char = "#" + floor.anchored = True + floor.position = vector2(5, 9) + floor.gravity = 0 + floor.bcolor = color3(255, 255, 255) + testgame.addobj(object) + testgame.addobj(floor) + testgame.render() + print(object.ID) + while True: + testgame.calcphysobj(object) + testgame.calcphysobj(floor) + testgame.render() + time.sleep(0) + + diff --git a/testbuilds/plugin/out/main.py b/testbuilds/plugin/out/main.py new file mode 100644 index 0000000..e1a384e --- /dev/null +++ b/testbuilds/plugin/out/main.py @@ -0,0 +1,7 @@ + +import player +import ast +file = open("game.HEGF", 'r') +file = file.read() +file = ast.literal_eval(file) +player.execgame(file, fAPIPLUG=player.loadplugins(False)) diff --git a/testbuilds/plugin/out/mtTkinter.py b/testbuilds/plugin/out/mtTkinter.py new file mode 100644 index 0000000..87bedc7 --- /dev/null +++ b/testbuilds/plugin/out/mtTkinter.py @@ -0,0 +1,230 @@ +'''Thread-safe version of tkinter. + +Copyright (c) 2014, Andrew Barnert + +Based on mtTkinter (for Python 2.x), copyright (c) 2009, Allen B. Taylor + +This module is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser Public License for more details. + +You should have received a copy of the GNU Lesser Public License +along with this program. If not, see . + +Usage: + + import mttkinter as tkinter + # Use "t." as usual. + +or + + from mtt import * + # Use tkinter module definitions as usual. + +This module modifies the original tkinter module in memory, making all +functionality thread-safe. It does this by wrapping the Tk class' tk +instance with an object that diverts calls through an event queue when +the call is issued from a thread other than the thread in which the Tk +instance was created. The events are processed in the creation thread +via an 'after' event. + +The modified Tk class accepts two additional keyword parameters on its +__init__ method: + mtDebug: + 0 = No debug output (default) + 1 = Minimal debug output + ... + 9 = Full debug output + mtCheckPeriod: + Amount of time in milliseconds (default 100) between checks for + out-of-thread events when things are otherwise idle. Decreasing + this value can improve GUI responsiveness, but at the expense of + consuming more CPU cycles. + +Note that, because it modifies the original tkinter module (in memory), +other modules that use tkinter (e.g., Pmw) reap the benefits automagically +as long as mttkinter is imported at some point before extra threads are +created. + +Author: Allen B. Taylor, a.b.taylor@gmail.com +''' +import sys +import threading +if sys.version_info[0] == 2: + # Python 2 + from Tkinter import * + import Queue as queue +else: + # Python 3 + from tkinter import * + import queue + + +class _Tk(object): + """Wrapper for underlying attribute tk of class Tk""" + + def __init__(self, tk, mt_debug=0, mt_check_period=10): + """ + :param tk: Tkinter.Tk.tk Tk interpreter object + :param mt_debug: Determines amount of debug output. + 0 = No debug output (default) + 1 = Minimal debug output + ... + 9 = Full debug output + :param mt_check_period: Amount of time in milliseconds (default + 10) between checks for out-of-thread events when things are + otherwise idle. Decreasing this value can improve GUI + responsiveness, but at the expense of consuming more CPU + cycles. + + # TODO: Replace custom logging functionality with standard + # TODO: logging.Logger for easier access and standardization + """ + self._tk = tk + + # Create the incoming event queue + self._event_queue = queue.Queue(1) + + # Identify the thread from which this object is being created + # so we can tell later whether an event is coming from another + # thread. + self._creation_thread = threading.current_thread() + + # Create attributes for kwargs + self._debug = mt_debug + self._check_period = mt_check_period + # Destroying flag to be set by the .destroy() hook + self._destroying = False + + def __getattr__(self, name): + """ + Diverts attribute accesses to a wrapper around the underlying tk + object. + """ + return _TkAttr(self, getattr(self._tk, name)) + + +class _TkAttr(object): + """Thread-safe callable attribute wrapper""" + + def __init__(self, tk, attr): + self._tk = tk + self._attr = attr + + def __call__(self, *args, **kwargs): + """ + Thread-safe method invocation. Diverts out-of-thread calls + through the event queue. Forwards all other method calls to the + underlying tk object directly. + """ + + # Check if we're in the creation thread + if threading.current_thread() == self._tk._creation_thread: + # We're in the creation thread; just call the event directly + if self._tk._debug >= 8 or \ + self._tk._debug >= 3 and self._attr.__name__ == 'call' and \ + len(args) >= 1 and args[0] == 'after': + print('Calling event directly:', self._attr.__name__, args, kwargs) + return self._attr(*args, **kwargs) + else: + if not self._tk._destroying: + # We're in a different thread than the creation thread; + # enqueue the event, and then wait for the response. + response_queue = queue.Queue(1) + if self._tk._debug >= 1: + print('Marshalling event:', self._attr.__name__, args, kwargs) + self._tk._event_queue.put((self._attr, args, kwargs, response_queue), True, 1) + is_exception, response = response_queue.get(True, None) + + # Handle the response, whether it's a normal return value or + # an exception. + if is_exception: + ex_type, ex_value, ex_tb = response + raise ex_type(ex_value, ex_tb) + return response + + +def _Tk__init__(self, *args, **kwargs): + """ + Hook for Tkinter.Tk.__init__ method + :param self: Tk instance + :param args, kwargs: Arguments for Tk initializer + """ + # We support some new keyword arguments that the original __init__ method + # doesn't expect, so separate those out before doing anything else. + new_kwnames = ('mt_check_period', 'mt_debug') + new_kwargs = { + kw_name: kwargs.pop(kw_name) for kw_name in new_kwnames + if kwargs.get(kw_name, None) is not None + } + + # Call the original __init__ method, creating the internal tk member. + self.__original__init__mtTkinter(*args, **kwargs) + + # Replace the internal tk member with a wrapper that handles calls from + # other threads. + self.tk = _Tk(self.tk, **new_kwargs) + + # Set up the first event to check for out-of-thread events. + self.after_idle(_check_events, self) + + +# Define a hook for class Tk's destroy method. +def _Tk_destroy(self): + self.tk._destroying = True + self.__original__destroy() + + +def _check_events(tk): + """Checks events in the queue on a given Tk instance""" + + used = False + try: + # Process all enqueued events, then exit. + while True: + try: + # Get an event request from the queue. + method, args, kwargs, response_queue = tk.tk._event_queue.get_nowait() + except queue.Empty: + # No more events to process. + break + else: + # Call the event with the given arguments, and then return + # the result back to the caller via the response queue. + used = True + if tk.tk._debug >= 2: + print('Calling event from main thread:', method.__name__, args, kwargs) + try: + response_queue.put((False, method(*args, **kwargs))) + except SystemExit: + raise # Raises original SystemExit + except Exception: + # Calling the event caused an exception; return the + # exception back to the caller so that it can be raised + # in the caller's thread. + from sys import exc_info # Python 2 requirement + ex_type, ex_value, ex_tb = exc_info() + response_queue.put((True, (ex_type, ex_value, ex_tb))) + finally: + # Schedule to check again. If we just processed an event, check + # immediately; if we didn't, check later. + if used: + tk.after_idle(_check_events, tk) + else: + tk.after(tk.tk._check_period, _check_events, tk) + + +"""Perform in-memory modification of Tkinter module""" +# Replace Tk's original __init__ with the hook. +Tk.__original__init__mtTkinter = Tk.__init__ +Tk.__init__ = _Tk__init__ + +# Replace Tk's original destroy with the hook. +Tk.__original__destroy = Tk.destroy +Tk.destroy = _Tk_destroy diff --git a/testbuilds/plugin/out/player.py b/testbuilds/plugin/out/player.py new file mode 100644 index 0000000..35e02e3 --- /dev/null +++ b/testbuilds/plugin/out/player.py @@ -0,0 +1,1262 @@ +import sys +# Justus Jan Nico Wolff +sys.dont_write_bytecode = True +stderrfile = open("errors.log", 'w') +stderrfile.write("") +stderrfile.close() +stderrfile = open("errors.log", 'a') +#sys.stderr = stderrfile +import subprocess +import pkg_resources +import ast + +required = open("requirements.txt", 'r') +required = ast.literal_eval(required.read()) +installed = {pkg.key for pkg in pkg_resources.working_set} +missing = required - installed + +if missing: + while True: + ans = input(f"Einige benötigte Module sind nicht installiert ({missing}), installieren per pip? Y/N ") + if ans == "y": + python = sys.executable + subprocess.check_call([python, '-m', 'pip', 'install', *missing], stdout=subprocess.DEVNULL) + break + elif ans == "n": + exit() +import mtTkinter as tk +from tkinter import ttk as tkk +from tkinter import messagebox +from tkinter import filedialog +import copy +import hashengine +def norms(): + global LH + import PCPL + import langsys + PCPL.interpreter.ENG = hashengine + LH = langsys.langhandler() + lang = open("clang", 'r') + lang = lang.read() + LH.setlang(lang) + # LH.string("") +def replacelh(): + global LH + class rLH: + def __init__(self): + pass + def string(self, target): + return target + def getlangs(self): + return () + LH = rLH() +if __name__ == "__main__": + norms() +else: + replacelh() +if len(sys.argv) == 2: + if sys.argv[1] == "NOLANG": + replacelh() +import time +import shutil +import os +import random +import string +import easygui +import base64 +import simpleaudio as sa +import multiprocessing +import threading + +global gamedata +global cooldown +global version +global plugindir +version = "HE2.2-Hashengine V2.2" +#plugindir = "plugins" +plugindir = "testplugins" +cooldown = False +gamedata = {} + +def prepspecified(target): + out = [] + tempwin = tk.Tk() + bar = tkk.Progressbar(tempwin) + bar.place(width=200) + ptext = tk.Label(tempwin, text="NONE") + ptext.place() + count = 1 + modellist = {} + for i in target: + id = i + i = gamedata[i] + ptext.config(text=i["name"]) + bar.step(count/len(gamedata)) + count += 1 + tempwin.update() + temp = {"id": i["id"], "name": i["name"], "SID": i["SID"],} + tempargs = {} + tosearch = {} + for arg in i["args"]: + if not arg in extypes and not arg in ignoreat and arg != "sdata": + tosearch[arg] = i["args"][arg] + continue + if arg in ignoreat and arg != "sdata": continue + if arg != "sdata": + tempargs[arg] = i["args"][arg] + else: + tempargs[arg] = str(base64.b64encode(i["args"][arg]), "ascii", "ignore") + for argname in tosearch: + arg = tosearch[argname] + temp2 = getattributes(arg) + temp2.update({"ARGID": arg._type}) + tempargs[argname] = temp2 + if objtree.parent(id) != "": + modelname = objtree.item(objtree.parent(id), "text") + if not modelname in list(modellist.keys()): + modellist[modelname] = [] + modellist[modelname].append(count-2) + temp["args"] = tempargs + out.append(temp) + tempwin.destroy() + return [out, modellist, gamexsize, gameysize] + +class script: + def __init__(self): + self.code = "" + + def execute(self, API, log): + #old code for PCPL code execution, replaced with python code execution + """ + PCPL.resetvar() + PCPL.LIS("HASHBASE") + PCPL.run(self.code)""" + try: + exec(self.code, API) + except Exception as e: + log(f"[GAME] Exception occured in script: {e}") + +class previewrend: + def __init__(self, size, cam, container, offset): + self._size = size + self._grid = {} + self._win = container + self._frame = tk.Frame(container) + self._posframe = tk.Frame(self._frame) + self._cam = cam + self._xcam = tk.Label(self._posframe, text="-") + self._ycam = tk.Label(self._posframe, text="-") + self._xcam.grid(row=size[1], column=0) + self._ycam.grid(row=size[1]+1, column=0) + + tkeys = list(string.ascii_letters) + self._keys = {} + for i in tkeys: + self._keys[i] = False + self._win.bind("", self.keypupd, add="+") + self._win.bind("", self.keydupd, add="+") + + for y in range(size[1]): + for x in range(size[0]): + temp = tk.Label(self._frame, text=" ", borderwidth=1, relief=tk.GROOVE, width=3) + temp.grid(row=y+offset[1], column=x+offset[0]+1) + self._win.update() + self._grid[f"{x}:{y}"] = temp + self._posframe.grid() + self._frame.grid() + + def destroy(self): + self._posframe.destroy() + self._frame.destroy() + + def keypupd(self, event): + event = event.char + if event in self._keys: + self._keys[event] = True + + def keydupd(self, event): + event = event.char + if event in self._keys: + self._keys[event] = False + + def getkeys(self): + return self._keys + + def coltohex(self, target): + colors = [] + target = [target.r, target.g, target.b] + for i in target: + colors.append(("0"*(2-len(hex(int(i))[2:])))+hex(i)[2:]) + out = "" + for i in colors: + out = out + i + return "#"+out + + def update(self): + self._win.update() + + def select(self, x, y): + if f"{int(x)}:{int(y)}" in self._grid: + self._grid[f"{int(x)}:{int(y)}"].config(background="cyan") + self.update() + + def deselect(self, x, y): + if f"{x}:{y}" in self._grid: + self._grid[f"{x}:{y}"].config(background="white") + self.update() + + def pix(self, x, y, text, bcolor, fcolor): + self._xcam.config(text=LH.string("xcam")+str(self._cam.position.x)) + self._ycam.config(text=LH.string("ycam")+str(self._cam.position.y)) + if f"{x}:{y}" in self._grid: + self._grid[f"{x}:{y}"].config(text=text, bg=self.coltohex(bcolor), fg=self.coltohex(fcolor)) + +class render: + def __init__(self, size, cam, container, offset): + self._size = size + self._grid = {} + self._win = container + self._frame = tk.Frame(container) + self._posframe = tk.Frame(self._frame) + self._cam = cam + self._xcam = tk.Label(self._posframe, text="-") + self._ycam = tk.Label(self._posframe, text="-") + #self._xcam.grid(row=size[1], column=0) + #self._ycam.grid(row=size[1]+1, column=0) + + tkeys = list(string.ascii_letters) + self._keys = {} + for i in tkeys: + self._keys[i] = False + self._win.bind("", self.keypupd) + self._win.bind("", self.keydupd) + + for y in range(size[1]): + for x in range(size[0]): + temp = tk.Label(self._frame, text=" ", width=3) + temp.grid(row=y+offset[1], column=x+offset[0]+1) + self._win.update() + self._grid[f"{x}:{y}"] = temp + self._posframe.grid() + self._frame.grid() + + def keypupd(self, event): + event = event.char + if event in self._keys: + self._keys[event] = True + + def keydupd(self, event): + event = event.char + if event in self._keys: + self._keys[event] = False + + def getkeys(self): + return self._keys + + def coltohex(self, target): + colors = [] + target = [target.r, target.g, target.b] + for i in target: + colors.append(("0"*(2-len(hex(int(i))[2:])))+hex(i)[2:]) + out = "" + for i in colors: + out = out + i + return "#"+out + + def update(self): + self._win.update() + + def pix(self, x, y, text, bcolor, fcolor): + self._xcam.config(text=LH.string("xcam")+str(self._cam.position.x)) + self._ycam.config(text=LH.string("ycam")+str(self._cam.position.y)) + if f"{x}:{y}" in self._grid: + self._grid[f"{x}:{y}"].config(text=text, bg=self.coltohex(bcolor), fg=self.coltohex(fcolor)) + +class nullrend: + def __init__(self, size, cam): + pass + + def getkeys(self): + pass + + def update(self): + pass + + def pix(self, x, y, text, bcolor, fcolor): + pass + +def selectlang(new): + lang = open("clang", 'w') + lang.write(new) + lang.close() + container.quit() + subprocess.Popen([sys.executable, __file__]) + +def add(objtype, parent="", render=True): + global objtree + obj = getattr(types, objtype)() + args = {} + for i in dir(obj): + if i.startswith("_"): continue + args[i] = getattr(obj, i) + temp = {"id": objtype, "args": args, "name": LH.string(objtype), "SID": genid()} + id = genid() + if GUIe == True: objtree.insert(parent, tk.END, text=LH.string(objtype), image=icons[objtype], iid=id, tags=("objsel")) + gamedata[id] = temp + if objtype in crucial: + preview.addobj(obj) + gamedata[id]["args"]["ID"] = obj.ID + if GUIe == True and render == True: preview.render() + return id + +def renameobj(): + target = objtree.focus() + if target == "": return + new = easygui.enterbox(LH.string("NN"), LH.string("rename")) + if new: + objtree.item(target, text=new) + if not "HASHMODEL" in objtree.item(target, "tags"): + gamedata[target]["name"] = new + +def modelobjs(): + #use detach and move + temp = objtree.selection() + targets = [] + for i in temp: + if not "objsel" in objtree.item(i, "tags"): continue + if objtree.parent(i) != "": continue + if gamedata[i]["id"] == "obj": + targets.append(i) + tempid = genid() + objtree.insert("", tk.END, text=LH.string("IOM"), image=icons["model"], iid=tempid, tags=("HASHMODEL",)) + for i in targets: + objtree.detach(i) + objtree.move(i, tempid, "end") + +def demodelobjs(): + #use detach and move + temp = objtree.focus() + if not "HASHMODEL" in objtree.item(temp, "tags"): return + for i in objtree.get_children(temp): + objtree.detach(i) + objtree.move(i, "", "end") + objtree.delete(temp) + +def delobjg(target): + objtree.delete(target) + temp = gamedata.pop(target) + if temp["id"] in crucial: + preview.removeobjbyid(temp["args"]["ID"]) + #preview.render() + +def delobj(): + target = objtree.selection() + if target == (): return + atritree.delete(*atritree.get_children()) + for i in target: + if "HASHMODEL" in objtree.item(i, "tags"): + tempwin = tk.Tk() + stat = tk.Label(tempwin, text="NONE") + stat.grid() + speed = tk.Label(tempwin, text="NONE") + speed.grid() + tempwin.update() + count = 0 + length = len(objtree.get_children(i)) + fpscount = 0 + timestamp = time.time() + fps = 0 + for f in objtree.get_children(i): + delobjg(f) + stat.config(text=f"{count}/{length}") + speed.config(text=f"{fps}/s") + tempwin.update() + count += 1 + fpscount += 1 + if time.time()-timestamp > 0.1: + fps = fpscount*10 + fpscount = 0 + timestamp = time.time() + objtree.delete(i) + tempwin.destroy() + else: + delobjg(i) + preview.render() + +def HMPC(event): + currentat = objtree.focus() + target = atritree.focus() + name = atritree.item(target, "text") + back = 0 + if name == "x": + back = aposx(0) + elif name == "y": + back = aposy(0) + for i in objtree.get_children(currentat): + if not "ID" in gamedata[i]["args"]: continue + setattr(gamedata[i]["args"]["position"], name, getattr(gamedata[i]["args"]["position"], name)+back) + setattr(preview.getobjbyid(gamedata[i]["args"]["ID"]).position, name, getattr(preview.getobjbyid(gamedata[i]["args"]["ID"]).position, name)+back) + updselect(None) + +def changemodelpos(event): + atritree.delete(*atritree.get_children()) + atritree.insert("", index=tk.END, text="x", tags=("OA",)) + atritree.insert("", index=tk.END, text="y", tags=("OA",)) + atritree.insert("", index=tk.END, text="SEQSID", values=(calcseqsid(objtree.focus())), tags=("SID",)) + +def calcseqsid(target): + out = "" + for f in objtree.get_children(target): + out = out + gamedata[f]["SID"][:5] + return out + +def rpopup(event): + try: + rmenu.tk_popup(event.x_root, event.y_root) + finally: + rmenu.grab_release() + +def getattributes(target): + out = {} + for i in dir(target): + if i.startswith("_"): continue + out[i] = getattr(target, i) + return out + +def copySID(event): + target = atritree.focus() + text = atritree.item(target, "values")[0] + container.clipboard_clear() + container.clipboard_append(text) + messagebox.showinfo(LH.string("done"), LH.string("copied")) + +def updatribute(event): + global currentat + target = objtree.focus() + currentat = target + atritree.delete(*atritree.get_children()) + for i in gamedata[target]["args"]: + if i in ignoreat: continue + if i in valtypes and not i in DCTE: + val = gamedata[target]["args"][i] + atritree.insert("", tk.END, text=i, values=(val), tags=("FA", )) + elif i in valtypes and i in DCTE: + atritree.insert("", tk.END, text=i, values=(""), tags=("FA", )) + else: + root = atritree.insert("", tk.END, text=i, tags=("FA", )) + temp = getattributes(gamedata[target]["args"][i]) + for f in temp: + atritree.insert(root, tk.END, text=f, values=(temp[f]), tags=("FA", )) + atritree.insert("", tk.END, text="SID", values=(gamedata[target]["SID"]), tags=("SID", )) + +def halatribute(event): + target = atritree.focus() + name = atritree.item(target, "text") + parent = atritree.parent(target) + currentobj = currentat + if name in valtypes: + if parent == "": + new = valtypes[name](gamedata[currentobj]["args"][name]) + gamedata[currentobj]["args"][name] = new + if "ID" in gamedata[currentobj]["args"]: + temp = preview.getobjbyid(gamedata[currentobj]["args"]["ID"]) + setattr(temp, name, new) + atritree.delete(*atritree.get_children()) + objtree.focus("") + else: + parent = atritree.item(parent, "text") + new = valtypes[name](getattr(gamedata[currentobj]["args"][parent], name)) + setattr(gamedata[currentobj]["args"][parent], name, new) + if "ID" in gamedata[currentobj]["args"]: + temp = preview.getobjbyid(gamedata[currentobj]["args"]["ID"]) + setattr(temp, name, new) + atritree.delete(*atritree.get_children()) + objtree.focus("") + updselect(None) + +def updatepreviewcam(char): + global cooldown + if cooldown == True: return + cooldown = True + char = char.char + allowed = ["w", "a", "s", "d"] + if not char in allowed: return + if char == "w": preview.camera.position += hashengine.vector2(y=1) + if char == "a": preview.camera.position += hashengine.vector2(x=1) + if char == "s": preview.camera.position -= hashengine.vector2(y=1) + if char == "d": preview.camera.position -= hashengine.vector2(x=1) + updselect(None) + time.sleep(.0) + cooldown = False + +def save(): + target = filedialog.asksaveasfile() + target.write(str(prepspecified(gamedata))) + target.close() + messagebox.showinfo(LH.string("suc"), LH.string("save-suc")) + +def clear(): + global gamedata + objtree.delete(*objtree.get_children()) + atritree.delete(*atritree.get_children()) + gamedata = {} + preview._objects = {} + preview.camera.position = hashengine.vector2() + preview.render() + +def importsound(target): + oid = add("rawsound") + gamedata[oid]["name"] = target["name"] + gamedata[oid]["args"]["sdata"] = base64.b64decode(target["args"]["sdata"]) + gamedata[oid]["args"]["spath"] = target["args"]["spath"] + if "SID" in target: + gamedata[oid]["SID"] = target["SID"] + if GUIe == True: objtree.item(oid, text=target["name"]) + +def importobj(target): + if target["id"] == "sound" or target["id"] == "rawsound": + importsound(target) + return + oid = add(target["id"]) + id = gamedata[oid] + id["name"] = target["name"] + if "SID" in target: + id["SID"] = target["SID"] + if GUIe == True: objtree.item(oid, text=target["name"]) + #create arguments + outargs = {} + for argname in target["args"]: + arg = target["args"][argname] + if isinstance(arg, dict): + ID = arg.pop("ARGID") + obj = attypes[ID]() + for i in arg: + setattr(obj, i, arg[i]) + arg = obj + outargs[argname] = arg + #apply arguments to obj + if target["id"] in crucial: + for i in outargs: + setattr(preview.getobjbyid(gamedata[oid]["args"]["ID"]), i, outargs[i]) + id["args"].update(outargs) + return oid + +def load(cleargame=True, GUI=True, path="", override=False): + if GUI == True: + file = filedialog.askopenfile() + elif override == False: + file = open(path, 'r') + tempwin = tk.Tk() + ptext = tk.Label(tempwin, text="NONE") + ptext.place(y=30) + stat = tk.Label(tempwin, text="NONE") + stat.place(y=50) + if override == False: + target = file.read() + file.close() + else: + target = override + target = ast.literal_eval(target) + if cleargame: + global models + models = [] + clear() + if len(target) == 0: return + if not isinstance(target[0], list): + #very old save file + count = 1 + bar = tkk.Progressbar(tempwin, maximum=len(target)) + bar.place(width=200) + for i in target: + ptext.config(text="Current: "+i["name"]) + bar.step() + stat.config(text=f"Object {count}/{len(target)}") + tempwin.update() + importobj(i) + count += 1 + tempwin.destroy() + preview.render() + elif len(target) < 3: + #old save file + count = 1 + bar = tkk.Progressbar(tempwin, maximum=len(target)) + bar.place(width=200) + ids = {} + for i in target[0]: + ptext.config(text="Current: "+i["name"]) + bar.step() + stat.config(text=f"Object {count}/{len(target)}") + tempwin.update() + id = importobj(i) + if id: + ids[count-1] = id + count += 1 + for i in target[1]: + tempid = genid() + if GUIe == True: objtree.insert("", tk.END, text=i, image=icons["model"], iid=tempid, tags=("HASHMODEL",)) + temp = [] + for f in target[1][i]: + if GUIe == True: + objtree.detach(ids[f]) + objtree.move(ids[f], tempid, "end") + temp.append(ids[f]) + models.append(temp) + tempwin.destroy() + preview.render() + else: + #new save file + global gamexsize + global gameysize + count = 1 + bar = tkk.Progressbar(tempwin, maximum=len(target)) + bar.place(width=200) + ids = {} + for i in target[0]: + ptext.config(text="Current: "+i["name"]) + bar.step() + stat.config(text=f"Object {count}/{len(target)}") + tempwin.update() + id = importobj(i) + if id: + ids[count-1] = id + count += 1 + for i in target[1]: + tempid = genid() + if GUIe == True: objtree.insert("", tk.END, text=i, image=icons["model"], iid=tempid, tags=("HASHMODEL",)) + temp = [] + for f in target[1][i]: + if GUIe == True: + objtree.detach(ids[f]) + objtree.move(ids[f], tempid, "end") + temp.append(ids[f]) + models.append(temp) + tempwin.destroy() + gamexsize = target[2] + gameysize = target[3] + if GUIe == True: initpreview() + preview.render() + +def export(): + temp = objtree.selection() + if temp == (): return + target = [] + for i in temp: + if "HASHMODEL" in objtree.item(i, "tags"): + target.extend(objtree.get_children(i)) + else: target.append(i) + back = prepspecified(target) + targetpath = filedialog.asksaveasfile() + if not targetpath: return + targetpath.write(str(back)) + targetpath.close() + messagebox.showinfo(LH.string("suc"), LH.string("save-suc")) + +def log(text, end="\n", flush=False): + global logfile + global clog + if clog: + if not os.path.exists("logs"): + os.mkdir("logs") + file = open("logs/"+logfile+".txt", 'a') + file.write(str(text)+end) + file.close() + +def NULL(): + pass + +class gsound: + def __init__(self, data, game): + self._playing = False + self._data = data + game.currentsounds.append(self) + + def play(self): + if self._playing == True: self.stop() + self._sound = sa.play_buffer(self._data, 2, 2, 44100) + self._playing = True + + def stop(self): + if self._playing == False: return + self._sound.stop() + self._playing = False + + def wait(self): + if self._playing == False: return + self._sound.wait_done() + +def testing(): + global testproc + global running + global clog + global APIPLUG + try: + if running == True: return + except: + pass + running = True + testproc = multiprocessing.Process(target=execgame, args=(prepspecified(gamedata), clog.get(), APIPLUG)) + testproc.start() + +def APIGEN(): + global APIPLUG + API = {"print": log, "HASHBASE": hashengine} + API["HASHGAME"] = maingame + API["SOUND"] = lambda data: gsound(data, maingame) + for i in APIPLUG: + exec(i, globals()) + temp = globals()["PLUGINAPIFUNC"]() + print(temp) + API.update(temp) + return API + +def run(): + global logfile + global maingame + global window + temp = time.gmtime(time.time()) + logfile = "" + for i in temp: + logfile = logfile + "S" + str(i) + log("Log file start!") + log(f"date: year: {temp[0]} month: {temp[1]} day: {temp[2]} hour: {temp[3]} min.: {temp[4]}, sec.: {temp[5]}") + log(f"Version: {version}") + log("Preparing API...") + log("Done!") + window = tk.Tk() + window.protocol("WM_DELETE_WINDOW", NULL) + maingame = hashengine.game(renderer=lambda size, cam: render(size, cam, window, [0, 0]), sounddir="/", size=[gamexsize, gameysize]) + log("main game initalised!") + log("copying sounds...") + for i in gamedata: + i = gamedata[i] + if i["id"] != "sound" and i["id"] != "rawsound": continue + maingame.sounds[i["args"]["spath"]] = i["args"]["sdata"] + #skip = [] + for i in gamedata: + GID = i + i = gamedata[i] + if i["id"] != "obj": continue + objid = i["args"]["ID"] + maingame._SIDS[i["SID"]] = objid + """if objtree.parent(GID) != "" and not objtree.parent(GID) in skip: + skip.append(objtree.parent(GID)) + out = "" + out2 = [] + for f in objtree.get_children(objtree.parent(GID)): + out = out + gamedata[f]["SID"][:5] + out2.append(f) + maingame._SEQSIDS[out] = out2""" + for i in models: + out = [] + SEQSID = "" + for f in i: + SEQSID = SEQSID + gamedata[f]["SID"][:5] + out.append(gamedata[f]["args"]["ID"]) + maingame._SEQSIDS[SEQSID] = out + + maingame._objects = copy.deepcopy(preview._objects) + """ + objects = copy.deepcopy(preview._objects) + maingame._objects = objects""" + scripts = [] + for i in gamedata: + i = gamedata[i] + if i["id"] != "script": continue + i = i["args"]["code"] + obj = script() + obj.code = i + scripts.append(obj) + log("objects transferred!") + #gamescript = """ +#global HASHGAME +#import time +#while True: +# for i in HASHGAME._objects: +# i = HASHGAME._objects[i] +# HASHGAME.calcphysobj(i) +# HASHGAME.render() +#""" + #gameloopsc = script() + #gameloopsc.code = gamescript + #maingame.startscript(lambda: gameloopsc.execute(APIGEN(), log)) + log("game test started!!!") + log("---------------------") + for i in scripts: + maingame.startscript(lambda: i.execute(APIGEN(), log)) + #window.mainloop() + while True: + for i in maingame._objects: + i = maingame._objects[i] + maingame.calcphysobj(i) + maingame.render() + window.update() + +def muladd(target): + for i in range(10): + add(target) + +def stoptest(): + global testproc + global running + try: + if running == False: return + except: + return + testproc.terminate() + running = False + +def build(): + print("asking user for output directory...") + target = filedialog.askdirectory() + os.mkdir(target+"/out") + target = target+"/out" + print("building started") + print("generating HEGF file...") + hegf = str(prepspecified(gamedata)) + file = open(target+"/game.HEGF", 'w') + file.write(hegf) + file.close() + print("done.") + print("copying files...") + tocopy = ["mtTkinter.py", "hashengine.py"] + for i in tocopy: + print(f"copying {i}...") + shutil.copyfile(i, target+"/"+i) + shutil.copyfile(__file__, target+"/"+"player.py") + shutil.copyfile("requirements.txt", target+"/"+"requirements.txt") + shutil.copytree(plugindir, target+"/"+plugindir) + file = open(target+"/main.py", 'w') + file.write(""" +import player +import ast +file = open("game.HEGF", 'r') +file = file.read() +file = ast.literal_eval(file) +player.execgame(file, fAPIPLUG=player.loadplugins(False)) +""") + print("done.") + print("building finished!") + +def importPS(): + target = filedialog.askopenfile() + if target: + temp = add("script") + gamedata[temp]["args"]["code"] = str(target.read()) + target.close() + +def genid(): + id = "" + chars = list(string.ascii_letters) + for i in range(255): + id = id + random.choice(chars) + return id + +def COBS(target: str, offset=hashengine.vector2(), ignore=[" ",]): + origin = target + target = target.split("\n") + tempid = genid() + tempwin = tk.Tk() + stat = tk.Label(tempwin, text="NONE") + stat.grid() + speed = tk.Label(tempwin, text="NONE") + speed.grid() + tempwin.update() + if GUIe == True: objtree.insert("", tk.END, text=LH.string("IOM"), image=icons["model"], iid=tempid, tags=("HASHMODEL",)) + count = 1 + fpscount = 0 + timestamp = time.time() + fps = 0 + for i in range(len(target)): + y = i + i = target[i] + for f in range(len(i)): + x = f + f = i[x] + stat.config(text=f"{count}/{len(origin)}") + speed.config(text=f"{fps}/s") + tempwin.update() + count += 1 + fpscount += 1 + if time.time()-timestamp > 0.1: + fps = fpscount*10 + fpscount = 0 + timestamp = time.time() + if f in ignore: + continue + temp = add("obj", tempid, False) + objtree.item(temp, text=f) + #gamedata[temp]["args"]["ID"] + gamedata[temp]["args"]["char"] = f + gamedata[temp]["name"] = f + setattr(preview.getobjbyid(gamedata[temp]["args"]["ID"]), "char", f) + gamedata[temp]["args"]["position"] = hashengine.vector2(x, y)+offset + setattr(preview.getobjbyid(gamedata[temp]["args"]["ID"]), "position", hashengine.vector2(x, y)+offset) + gamedata[temp]["args"]["anchored"] = True + setattr(preview.getobjbyid(gamedata[temp]["args"]["ID"]), "anchored", True) + gamedata[temp]["args"]["collide"] = True + setattr(preview.getobjbyid(gamedata[temp]["args"]["ID"]), "collide", True) + #preview.render() + updselect(None) + tempwin.destroy() + +def updselect(event): + preview.render() + selection = objtree.selection() + for selected in selection: + if "objsel" in objtree.item(selected, "tags"): + if gamedata[selected]["id"] == "obj": + preview._renderer.select(gamedata[selected]["args"]["position"].x+preview.camera.position.x, gamedata[selected]["args"]["position"].y+preview.camera.position.y) + if "HASHMODEL" in objtree.item(selected, "tags"): + for i in objtree.get_children(selected): + if "objsel" in objtree.item(i, "tags"): + if gamedata[i]["id"] == "obj": + preview._renderer.select(gamedata[i]["args"]["position"].x+preview.camera.position.x, gamedata[i]["args"]["position"].y+preview.camera.position.y) + +def changegamex(): + global gamexsize + gamexsize = int(aposx(gamexsize)) + initpreview() + +def changegamey(): + global gameysize + gameysize = int(aposy(gameysize)) + initpreview() + +def initpreview(): + global preview + objs = {} + try: + objs = preview._objects + preview._renderer.destroy() + ungridobjtrees() + except: + pass + preview = hashengine.game(renderer=lambda size, cam: previewrend(size, cam, container=container, offset=[0, 0]), sounddir="/", size=[gamexsize, gameysize]) + preview._objects = objs + try: + gridobjtrees() + except NameError: + pass + +def gridobjtrees(): + objtree.grid(row=1, column=0) + atritree.grid(row=2, column=0) + +def ungridobjtrees(): + objtree.grid_remove() + atritree.grid_remove() + +def loadplugins(GUIe=True): + global APIPLUG + for pluginname in os.listdir(plugindir): + file = open(plugindir+"/"+pluginname, 'r') + plugindata = file.read() + plugindata = ast.literal_eval(plugindata) + file.close() + if "init" in plugindata and GUIe == True: + exec(plugindata["init"], globals()) + if "API" in plugindata: + APIPLUG.append(plugindata["API"]) + return APIPLUG + +def GUIinit(): + global container + global objtree + global rmenu + global atritree + global currentat + global GUIe + global clog + global models + global menu + global filemenu + global addmenu + global testmenu + global buildmenu + global settings + global langmenu + models = [] + GUIe = True + container = tk.Tk() + container.bind("", updatepreviewcam, add="+") + + global icons + icons = {} + + for i in os.listdir("icons"): + icons[i.split(".")[0]] = tk.PhotoImage(file=f"icons/{i}") + + #preview init + initpreview() + + #tree init + objtree = tkk.Treeview(container, columns=("-")) + objtree.heading("#0", text=LH.string("objs")) + objtree.tag_bind("objsel", "<>", updatribute) + objtree.tag_bind("HASHMODEL", "<>", changemodelpos) + objtree.bind("<>", updselect, add="+") + objtree.grid(row=1, column=0) + + #attribute tree init + currentat = "temp" + atritree = tkk.Treeview(container, columns=("#1"), selectmode="browse") + atritree.heading("#0", text=LH.string("attribute")) + atritree.heading("#1", text=LH.string("attribute-val")) + atritree.tag_bind("FA", "", halatribute) + atritree.tag_bind("OA", "", HMPC) + atritree.tag_bind("SID", "", copySID) + atritree.grid(row=2, column=0) + + #right click menu + rmenu = tk.Menu(container, tearoff=0) + rmenu.add_command(label=LH.string("rename"), command=renameobj) + rmenu.add_command(label=LH.string("delete"), command=delobj) + rmenu.add_command(label=LH.string("modelobjs"), command=modelobjs) + rmenu.add_command(label=LH.string("demodelobjs"), command=demodelobjs) + + objtree.bind("", rpopup) + + #menu init + menu = tk.Menu(container) + container.config(menu=menu) + filemenu = tk.Menu(menu) + menu.add_cascade(label=LH.string("file"), menu=filemenu) + filemenu.add_command(label=LH.string("new"), command=clear) + filemenu.add_command(label=LH.string("open"), command=load) + filemenu.add_command(label=LH.string("save"), command=save) + filemenu.add_separator() + #create logs var + clog = tk.BooleanVar() + filemenu.add_checkbutton(label=LH.string("clog"), onvalue=1, offvalue=0, variable=clog) + filemenu.add_separator() + filemenu.add_command(label=LH.string("export"), command=export) + filemenu.add_command(label=LH.string("import"), command=lambda: load(False)) + filemenu.add_separator() + filemenu.add_command(label=LH.string("exit"), command=container.quit) + + addmenu = tk.Menu(menu) + menu.add_cascade(label=LH.string("add"), menu=addmenu) + addmenu.add_command(label=LH.string("obj"), command=lambda: add("obj")) + addmenu.add_command(label=LH.string("script"), command=lambda: add("script")) + addmenu.add_command(label=LH.string("sound"), command=lambda: add("sound")) + addmenu.add_separator() + addmenu.add_command(label=LH.string("IPS"), command=importPS) + addmenu.add_command(label=LH.string("COBS"), command=lambda: COBS(acode(" "))) + #addmenu.add_command(label=LH.string("obj"), command=lambda: muladd("obj")) + + testmenu = tk.Menu(menu) + menu.add_cascade(label=LH.string("testing"), menu=testmenu) + testmenu.add_command(label=LH.string("test"), command=testing, image=icons["test"], compound="left") + testmenu.add_command(label=LH.string("stest"), command=stoptest, image=icons["stop-test"], compound="left") + + buildmenu = tk.Menu(menu) + menu.add_cascade(label=LH.string("building"), menu=buildmenu) + buildmenu.add_command(label=LH.string("build"), command=build, image=icons["build"], compound="left") + + settings = tk.Menu(menu) + menu.add_cascade(label=LH.string("settings"), menu=settings) + settings.add_command(label=LH.string("gamex"), command=changegamex) + settings.add_command(label=LH.string("gamey"), command=changegamey) + + langmenu = tk.Menu(menu) + menu.add_cascade(label=LH.string("langs"), menu=langmenu) + for i in LH.getlangs(): + langmenu.add_command(label=i, command=lambda i=i: selectlang(i)) + + container.mainloop() + +# attribute changers +def ats(mode, old): + #mode 0 = string + #mode 1 = single character + out = easygui.enterbox(LH.string("newval"), LH.string("newval")) + if out: + if mode == 1 and len(out) != 1: + messagebox.showerror(LH.string("error"), LH.string("SCE")) + return "N" + return out + else: + return old + +def anum(old): + out = easygui.enterbox(LH.string("newval"), LH.string("newval")) + if out: + if "." in out: + try: + out = float(out) + return out + except ValueError: + return old + else: + return int(out) + else: + return old + +def abool(old): + out = easygui.boolbox(LH.string("newval"), LH.string("newval"), (LH.string("true"), LH.string("false"))) + return out + +def acode(old): + out = easygui.textbox(LH.string("newval"), LH.string("newval"), old) + if out: + return out + else: + return old + +def apath(old, ext): + new = filedialog.askopenfilename(defaultextension=ext, filetypes=(ext)) + if new: + return new + else: + return old + +def aNULL(old): + return old + +def aposdone(): + global wait + wait = False + +def aposx(old): + global wait + wait = True + temp = tk.Toplevel() + butframe = tk.Frame(temp) + currentvar = tk.DoubleVar(temp, value=old) + current = tk.Entry(temp, textvariable=currentvar) + current.grid(row=0) + b1 = tk.Button(butframe, image=icons["ar-left"], command=lambda: currentvar.set(currentvar.get()-1)) + b2 = tk.Button(butframe, image=icons["ar-right"], command=lambda: currentvar.set(currentvar.get()+1)) + b1.grid(row=0, column=0) + b2.grid(row=0, column=1) + butframe.grid(row=1) + b3 = tk.Button(temp, text=LH.string("done"), command=aposdone) + b3.grid(row=3) + while wait == True: + temp.update() + tempvar = currentvar.get() + temp.destroy() + numbers = list(string.digits) + numbers.append("-") + numbers.append(".") + for i in str(tempvar): + if not i in numbers: + return old + return tempvar + +def aposy(old): + global wait + wait = True + temp = tk.Toplevel() + butframe = tk.Frame(temp) + currentvar = tk.DoubleVar(temp, value=old) + current = tk.Entry(temp, textvariable=currentvar) + current.grid(row=0) + b1 = tk.Button(butframe, image=icons["ar-up"], command=lambda: currentvar.set(currentvar.get()-1)) + b2 = tk.Button(butframe, image=icons["ar-down"], command=lambda: currentvar.set(currentvar.get()+1)) + b1.grid(row=0, column=0) + b2.grid(row=1, column=0) + butframe.grid(row=1) + b3 = tk.Button(temp, text=LH.string("done"), command=aposdone) + b3.grid(row=3) + while wait == True: + temp.update() + tempvar = currentvar.get() + temp.destroy() + numbers = list(string.digits) + numbers.append("-") + numbers.append(".") + for i in str(tempvar): + if not i in numbers: + return old + return tempvar + +def execgame(gametree, shouldlog=True, fAPIPLUG=[]): + global GUIe + global preview + global clog + global models + global APIPLUG + APIPLUG = fAPIPLUG + preview = hashengine.game(renderer=nullrend, sounddir="/") + GUIe = False + models = [] + load(False, False, "", str(gametree)) + clog = shouldlog + run() + +def cmd(): + print(f"hashengine version: {version}") + print("CMD") + while True: + cmd = input("> ") + try: + exec(cmd, globals()) + except Exception as e: + print(e) + +class rsound: + def __init__(self): + self.spath = "" + self.sdata = b"" + +class sound(): + def __init__(self, new): + self.spath = os.path.basename(new).split(".")[0] + self.sdata = hashengine.loadsound(new) + +global types +global ignoreat +global valtypes +global extypes +global attypes +global crucial +global DCTE +global gamexsize +global gameysize +global APIPLUG +APIPLUG = [] +gamexsize = 10 +gameysize = 10 +crucial = ["obj"] +types = hashengine.enum({"obj": hashengine.obj, "script": script, "rawsound": rsound, "sound": lambda: sound(apath("", [("Wave files", ".wave .wav")]))}) +ignoreat = ["ID", "execute", "sdata"] +DCTE = ["code"] +"""self.position = vector2() +self.char = " " +self.ID = 0 +self.gravity = 0 +self.acceleration = vector2() +self.velocity = vector2() +self.friction = 0 +self.collide = True +self.touch = True +self.anchored = False +self.bcolor = color3(255, 255, 255) +self.fcolor = color3()""" +valtypes = { +"char": lambda old: ats(1, old), +"gravity": anum, +"x": aposx, #anum +"y": aposy, #anum +"z": anum, +"r": anum, +"g": anum, +"b": anum, +"friction": anum, +"collide": abool, +"touch": abool, +"anchored": abool, +"code": acode, +"spath": aNULL, +} +#lambda old: apath(old, [("Wave files", ".wave .wav")]) +extypes = list(valtypes.keys()) +attypes = { +"vector2": hashengine.vector2, +"color3": hashengine.color3, +} +if __name__ == "__main__": + threading.Thread(target=cmd).start() + GUIinit() +stderrfile.close() \ No newline at end of file diff --git a/testbuilds/plugin/out/requirements.txt b/testbuilds/plugin/out/requirements.txt new file mode 100644 index 0000000..2cbc963 --- /dev/null +++ b/testbuilds/plugin/out/requirements.txt @@ -0,0 +1 @@ +{"easygui", "simpleaudio"} \ No newline at end of file diff --git a/testbuilds/plugin/out/testplugins/base.plg b/testbuilds/plugin/out/testplugins/base.plg new file mode 100644 index 0000000..b834012 --- /dev/null +++ b/testbuilds/plugin/out/testplugins/base.plg @@ -0,0 +1,16 @@ +{ +"name": "pluginname", +"permissions": [ + 1, + 2, + ], +"API": """ +def PLUGINAPIFUNC(): + return {"plugin": "Enabled"} +""", +"init": """ +addmenu = tk.Menu(menu) +menu.add_cascade(label="exampleplugin", menu=addmenu) +addmenu.add_command(label="NULL", command=print()) +""", +} \ No newline at end of file diff --git a/testplugins/base.plg b/testplugins/base.plg new file mode 100644 index 0000000..b834012 --- /dev/null +++ b/testplugins/base.plg @@ -0,0 +1,16 @@ +{ +"name": "pluginname", +"permissions": [ + 1, + 2, + ], +"API": """ +def PLUGINAPIFUNC(): + return {"plugin": "Enabled"} +""", +"init": """ +addmenu = tk.Menu(menu) +menu.add_cascade(label="exampleplugin", menu=addmenu) +addmenu.add_command(label="NULL", command=print()) +""", +} \ No newline at end of file diff --git a/tests/plugintest b/tests/plugintest new file mode 100644 index 0000000..5751f16 --- /dev/null +++ b/tests/plugintest @@ -0,0 +1 @@ +[[{'id': 'script', 'name': 'Skript', 'SID': 'pESBBUtxhoOcWqInhNTdqqqxonEoVbvhJQjHsuSCvsKorTpsPXvWbfElhVTeFmwSZPkFdgBQIoeHNfiBmqXjnRzeIIyokRTJtXXeSyIkxyWqYDOnwNWnVFtWXWoLPJkfcxLFKNJvdClNbgEcZNWJOqgPbTfunPEFeXzUyFYZadEeCIndNkYhWNjaZkIhhEKvdgVCjrLjmpbosAvOeSlhxEyTfJnrBgJgMSMeJOSPQRuMQMwZgjyCSldZtPJuiXA', 'args': {'code': 'from tkinter import messagebox\nmessagebox.showinfo(plugin, plugin)'}}], {}, 10, 10] \ No newline at end of file