Compare commits

..

No commits in common. "packages" and "main" have entirely different histories.

70 changed files with 136 additions and 416 deletions

View File

@ -1,15 +1,9 @@
import argparse import argparse
from source.pkgexplore import start_explorer from source.game import start_game
def main():
parser = argparse.ArgumentParser(prog='MBH', description='Morrowind with Blackjack and Hookers', epilog='?')
parser.add_argument('-m', '--main', required=True)
parser.add_argument('-p', '--package', nargs='+')
parser.add_argument('-e', '--explore', action='store_true')
args = parser.parse_args()
if args.explore:
start_explorer(args)
if __name__ == '__main__': if __name__ == '__main__':
main() parser = argparse.ArgumentParser(prog='MBH', description='Morrowind with Blackjack and hookers', epilog='?')
parser.add_argument('-p', '--package', nargs='+')
args = parser.parse_args()
start_game(args.package)

View File

@ -1,5 +1,4 @@
{ {
"name": "main-assets", "name": "main-assets",
"version": "0.1-alpha", "version": "0.1-alpha"
"type": "main"
} }

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 988 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 458 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 526 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 633 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

100
source/filesystem.py Normal file
View File

@ -0,0 +1,100 @@
import os
import posixpath
import json
from panda3d.core import VirtualFileSystem
class PackageNotFound(FileNotFoundError):
pass
class PackageMetaNotFound(FileNotFoundError):
pass
class PackageInvalid(FileNotFoundError):
pass
class PackageDuplicate(FileNotFoundError):
pass
class Package:
def __init__(self, name, vfs_dir):
self.name = name
self.vfs_dir = vfs_dir
class Filesystem:
def __init__(self):
self.package_list = []
self.package_map = {}
self.vfs = VirtualFileSystem.getGlobalPtr()
self.include('assets/')
self.mdl_missing = loader.loadModel('/main-assets/models/missing.glb')
self.mdl_missing.setTexture(loader.loadTexture('/main-assets/textures/missing_mdl.png'))
self.tex_missing = loader.loadTexture('/main-assets/textures/missing_tex.png')
def include(self, path):
if not os.path.exists(path):
raise PackageNotFound(path)
gamepkg_path = os.path.join(path, 'gamepackage.json')
if not os.path.exists(gamepkg_path):
raise PackageMetaNotFound(path)
meta = json.load(open(gamepkg_path))
if 'name' not in meta:
raise PackageInvalid(path)
if meta['name'] in self.package_map:
raise PackageDuplicate(meta['name'])
vfs_path = f'/{meta["name"]}'
vfs.mount(path, f'/{meta["name"]}', 0)
new_package = Package(meta["name"], vfs_path)
self.package_map[meta['name']] = new_package
self.package_list.insert(0, new_package)
def find_texture(self, tex_path):
try:
if tex_path[0:2] == '$/':
for package in self.package_list:
path = posixpath.join('/', package.name, 'textures', tex_path[2:])
if self.vfs.exists(path):
return loader.loadTexture(path)
return self.tex_missing
else:
resource_path = model_path.split('/')
path = posixpath.join('/', resource_path[0], 'textures', '/'.join(resource_path[1:]))
return loader.loadTexture(path)
except IOError:
return self.tex_missing
def find_model(self, model_path):
try:
if model_path[0:2] == '$/':
for package in self.package_list:
path = posixpath.join('/', package.name, 'models', model_path[2:])
if self.vfs.exists(path):
return loader.loadModel(path)
return self.mdl_missing
else:
resource_path = model_path.split('/')
path = posixpath.join('/', resource_path[0], 'models', '/'.join(resource_path[1:]))
return loader.loadModel(path)
except IOError:
return self.mdl_missing

30
source/game.py Normal file
View File

@ -0,0 +1,30 @@
from .filesystem import Filesystem
from direct.showbase.ShowBase import ShowBase
from panda3d.core import WindowProperties
class Game(ShowBase):
def __init__(self, packages):
ShowBase.__init__(self)
#self.disableMouse()
self.fs = Filesystem()
for package in packages:
self.fs.include(package)
mdl2 = self.fs.find_model('$/arrangements/arrange1.glb')
mdl2.reparentTo(render)
mdl2.setTexture(self.fs.find_texture('$/sfsd.png'), 1)
mdl_missing = self.fs.find_model('$/kakahead')
mdl_missing.reparentTo(render)
winprops = WindowProperties()
winprops.setSize(1600, 900)
self.win.requestProperties(winprops)
def start_game(packages):
if not packages:
packages = []
game = Game(packages)
game.run()

View File

@ -1,2 +0,0 @@
from .manager import PackageManager

View File

@ -1,14 +0,0 @@
class FilepathNotFound(Exception):
pass
class GamePackageMissing(Exception):
pass
class GamePackageMalformed(Exception):
pass
class PackageAlreadyLoaded(Exception):
pass
class InvalidPackageType(Exception):
pass

View File

@ -1,157 +0,0 @@
import posixpath
import tkinter as tk
import tkinter.ttk as ttk
import simplepbr
import gltf
from direct.stdpy.file import walk
from panda3d.core import NodePath, DirectionalLight, TransparencyAttrib
def get_icons():
return type('', (object,), {
'app': tk.PhotoImage(file="ui/icons/application.png"),
'brick': tk.PhotoImage(file="ui/icons/brick.png"),
'bricks': tk.PhotoImage(file="ui/icons/bricks.png"),
'computer': tk.PhotoImage(file="ui/icons/computer.png"),
'folder': tk.PhotoImage(file="ui/icons/folder.png"),
'shape_group': tk.PhotoImage(file="ui/icons/shape_group.png"),
'pictures': tk.PhotoImage(file="ui/icons/pictures.png"),
'picture': tk.PhotoImage(file='ui/icons/picture.png'),
"page_red": tk.PhotoImage(file='ui/icons/page_red.png'),
'shape_square': tk.PhotoImage(file='ui/icons/shape_square.png'),
'color_wheel': tk.PhotoImage(file='ui/icons/color_wheel.png'),
'color_swatch': tk.PhotoImage(file='ui/icons/color_swatch.png'),
'link': tk.PhotoImage(file='ui/icons/link.png'),
'sound': tk.PhotoImage(file='ui/icons/sound.png'),
'chart_organisation': tk.PhotoImage(file='ui/icons/chart_organisation.png'),
'anchor': tk.PhotoImage(file='ui/icons/anchor.png'),
'shape_flip_horizontal': tk.PhotoImage(file='ui/icons/shape_flip_horizontal.png'),
'film': tk.PhotoImage(file='ui/icons/film.png'),
'user': tk.PhotoImage(file='ui/icons/user.png'),
'chart_line_link': tk.PhotoImage(file='ui/icons/chart_line_link.png'),
'music': tk.PhotoImage(file='ui/icons/music.png')
})()
def add_package_contents_to_tree(tree, package, type, name, extensions, icon_folder, icon_group, icon_item):
data_path = posixpath.join(package.mount_dir, type)
tree.insert(package.mount_dir, 'end', data_path, text=f' {name}', image=icon_group)
file_count = 0
for root, paths, files in walk(data_path):
for path in paths:
tree.insert(root, 'end', posixpath.join(root, path), text=f' {path}', image=icon_folder)
for file in files:
if file.lower().endswith(extensions):
file_count += 1
tree.insert(root, 'end', posixpath.join(root, file), text=f' {file}', image=icon_item)
tree.item(data_path, text=f' {name} ({file_count})')
def add_package_models_to_tree(tree, package, icons):
add_package_contents_to_tree(tree, package, 'models/', '3D Models', ('.glb', '.gltf', '.obj', '.egg'), icons.folder, icons.shape_group, icons.shape_square)
def add_package_textures_to_tree(tree, package, icons):
add_package_contents_to_tree(tree, package, 'textures/', 'Image Textures', ('.png', '.jpg', '.bmp'), icons.folder, icons.pictures, icons.picture)
def add_package_materials_to_tree(tree, package, icons):
add_package_contents_to_tree(tree, package, 'materials/', 'Materials', ('.json'), icons.folder, icons.color_wheel, icons.color_swatch)
def add_package_sounds_to_tree(tree, package, icons):
add_package_contents_to_tree(tree, package, 'sounds/', 'Sounds', ('.ogg', '.wav'), icons.folder, icons.sound, icons.sound)
def add_package_music_to_tree(tree, package, icons):
add_package_contents_to_tree(tree, package, 'music/', 'Music', ('.ogg', '.mp3'), icons.folder, icons.music, icons.music)
def add_package_to_tree(tree, package, icons):
add_package_models_to_tree(tree, package, icons)
add_package_textures_to_tree(tree, package, icons)
add_package_materials_to_tree(tree, package, icons)
add_package_sounds_to_tree(tree, package, icons)
add_package_music_to_tree(tree, package, icons)
def init_packages_tree(tree, packages, icons):
tree.insert('', 'end', '/', text=' /', open=True, image=icons.app)
tree.insert('/', 'end', '/main/', text=f' Main Package "{packages.main_package.meta.name}" - {packages.main_package.meta.version}', image=icons.computer)
tree.insert('/', 'end', '/include/', text=f' Included Packages ({len(packages.package_list)})', image=icons.bricks)
add_package_to_tree(tree, packages.main_package, icons)
for package in packages.package_list:
tree.insert('/include/', 'end', package.mount_dir, text=f' "{package.meta.name}" - {package.meta.version}', image=icons.brick)
add_package_to_tree(tree, package, icons)
return tree
def init_gui(app, args):
app.gui.geometry("700x500")
app.gui.grid_rowconfigure(0, weight=10)
app.gui.grid_rowconfigure(1, weight=5)
app.gui.grid_columnconfigure(0, weight=1)
package_frame = tk.Frame(app.gui, padx=4, pady=4)
package_frame.grid(column=0, row=0, sticky="news")
package_frame.grid_columnconfigure(0, weight=1)
package_frame.grid_rowconfigure(0, weight=1)
package_frame.grid_rowconfigure(1, weight=1000)
packages_label = tk.Label(package_frame, text="Packages")
packages_label.grid(column=0, row=0, sticky="w")
packagetree_frame = tk.Frame(package_frame)
packagetree_frame.grid(column=0, row=1, sticky="news")
packagetree_frame.grid_columnconfigure(0, weight=1000)
packagetree_frame.grid_columnconfigure(1, weight=1)
packagetree_frame.grid_rowconfigure(0, weight=1)
app.tree_packages = ttk.Treeview(packagetree_frame, show="tree")
app.tree_packages.grid(column=0, row=0, sticky="news")
packagetree_scrollbar = ttk.Scrollbar(packagetree_frame, orient="vertical", command=app.tree_packages.yview)
packagetree_scrollbar.grid(column=1, row=0)
app.tree_packages.bind('<Double-1>', app.on_packagetree_doubleclick)
scene_frame = tk.Frame(app.gui, padx=4, pady=4)
scene_frame.grid(column=0, row=1, sticky="news")
scene_frame.grid_columnconfigure(0, weight=1)
scene_frame.grid_rowconfigure(0, weight=1)
scene_frame.grid_rowconfigure(1, weight=1000)
scene_label = tk.Label(scene_frame, text="Scene Graph")
scene_label.grid(column=0, row=0, sticky="w")
scenetree_frame = tk.Frame(scene_frame)
scenetree_frame.grid(column=0, row=1, sticky="news")
scenetree_frame.grid_columnconfigure(0, weight=10000)
scenetree_frame.grid_columnconfigure(1, weight=1)
scenetree_frame.grid_rowconfigure(0, weight=1)
app.tree_scene = ttk.Treeview(scenetree_frame, show="tree")
app.tree_scene.grid(column=0, row=0, sticky="news")
scenetree_scrollbar = ttk.Scrollbar(scenetree_frame, orient="vertical", command=app.tree_scene.yview)
scenetree_scrollbar.grid(column=1, row=0)
app.gui.protocol("WM_DELETE_WINDOW", app.on_tk_close)
def init_panda(app, args):
gltf.patch_loader(app.panda.loader)
app.panda.setBackgroundColor(.02, .02, .02)
app.panda.disableMouse()
app.panda.camera.setPos(0, -5, 2.5)
app.panda.camera.setHpr(0, -20, 0)
simplepbr.init()
app.env_root = NodePath('env_root')
app.env_root.reparentTo(app.panda.render)
axis = app.panda.loader.loadModel(app.package_manager.get_model_path('axis.glb'))
axis.setTransparency(TransparencyAttrib.MAlpha)
axis.setAlphaScale(0.2)
axis.reparentTo(app.env_root)
plane = app.panda.loader.loadModel(app.package_manager.get_model_path('debugplane.glb'))
plane.setTransparency(TransparencyAttrib.MAlpha)
plane.setAlphaScale(0.2)
plane.reparentTo(app.env_root)
root = app.panda.loader.loadModel(app.package_manager.get_model_path('debugrot.glb'))
root.setTransparency(TransparencyAttrib.MAlpha)
root.setAlphaScale(0.2)
root.reparentTo(app.env_root)
dlight = DirectionalLight('light')
dlight.setColor((4, 4, 4, 1))
dlight.setShadowCaster(True, 512, 512)
nplight = app.env_root.attachNewNode(dlight)
nplight.setPos(0, 1, 1)
nplight.setHpr(180+15, -45, 15)
app.camera_root = NodePath('camera_root')
app.camera_root.reparentTo(app.panda.render)
app.panda.camera.wrtReparentTo(app.camera_root)
app.panda.render.setLight(nplight)
app.scene_root = NodePath('scene_root')
app.scene_root.reparentTo(app.panda.render)
app.panda.taskMgr.add(app.update_input, "update_input")
app.panda.accept('mouse1', app.mouse1_down, [])
app.panda.accept('mouse1-up', app.mouse1_up, [])
app.panda.accept('wheel_up', app.wheel_up, [])
app.panda.accept('wheel_down', app.wheel_down, [])

View File

@ -1,57 +0,0 @@
import os, json
import posixpath
from .package import Package
from .errors import *
class PackageManager:
def __init__(self, vfs):
self.vfs = vfs
self.package_map = {}
self.package_list = []
self.main_package = None
def unmount_all(self):
self.vfs.unmount(self.main_package.mount_dir)
for package in self.package_list:
self.vfs.unmount(package.mount_dir)
def get_package_meta(self, filepath):
if not os.path.exists(filepath):
raise FilepathNotFound(filepath)
gamepkg_path = os.path.join(filepath, 'gamepackage.json')
if not os.path.exists(gamepkg_path):
raise GamePackageMissing(gamepkg_path)
meta = json.load(open(gamepkg_path))
if 'name' not in meta or 'type' not in meta:
raise GamePackageMalformed(gamepkg_path)
return meta
def set_main_package(self, filepath):
meta = self.get_package_meta(filepath)
if meta['type'] != 'main':
raise InvalidPackageType(filepath, meta['type'])
vfs_path = f'/main/'
self.vfs.mount(filepath, vfs_path, 0)
new_package = Package(type('', (object,), meta), vfs_path, self.vfs)
self.main_package = new_package
def include_package(self, filepath):
meta = self.get_package_meta(filepath)
if meta['name'] in self.package_map:
raise PackageAlreadyLoaded(meta['name'])
vfs_path = f'/include/{meta["name"]}'
self.vfs.mount(filepath, vfs_path, 0)
new_package = Package(type('', (object,), meta), vfs_path, self.vfs)
self.package_map[meta['name']] = new_package
self.package_list.insert(0, new_package)
def get_model_path(self, name):
for imported_package in reversed(self.package_list):
target_path = posixpath.join(imported_package.mount_dir, 'models', name)
if self.vfs.exists(target_path):
return target_path
main_path = posixpath.join(self.main_package.mount_dir, 'models', name)
if (self.vfs.exists(main_path)):
return main_path
return None

View File

@ -1,32 +0,0 @@
import posixpath
from direct.stdpy.file import walk
class Package:
def __init__(self, meta, mount_dir, vfs):
self.meta = meta
self.mount_dir = mount_dir
self.vfs = vfs
def all_models(self):
models_path = posixpath.join(self.mount_dir, 'models')
output = []
for root, paths, files in walk(models_path):
rel_root = root.replace(f'{models_path}', '').strip()
if len(rel_root) > 0 and rel_root[0] == '/':
rel_root = rel_root[1:]
for filepath in files:
output.append(posixpath.join(rel_root, filepath))
return output
def all_textures(self):
textures_path = posixpath.join(self.mount_dir, 'textures')
output = []
for root, paths, files in walk(textures_path):
rel_root = root.replace(f'{textures_path}', '').strip()
if len(rel_root) > 0 and rel_root[0] == '/':
rel_root = rel_root[1:]
for filepath in files:
output.append(posixpath.join(rel_root, filepath))
return output

View File

@ -1,136 +0,0 @@
import tkinter as tk
from source.packages import PackageManager
from source.packages.gui import *
from direct.showbase.ShowBase import ShowBase
from panda3d.core import VirtualFileSystem, Material, Vec3, CullFaceAttrib, WindowProperties
from direct.actor.Actor import Actor
class PackageExplorer:
def __init__(self, args):
self.gui = tk.Tk()
self.gui.title("Blackjack and Hookers: Package Explorer")
self.panda = ShowBase()
props = WindowProperties()
props.setTitle('Blackjack and Hookers: 3D Viewport')
self.panda.win.requestProperties(props)
self.package_manager = PackageManager(VirtualFileSystem.getGlobalPtr())
self.package_manager.set_main_package(args.main)
self.icons = get_icons()
self.prev = None
if args.package:
for include in args.package:
self.package_manager.include_package(include)
self.tree_packages = None
self.tree_scene = None
self.running = True
self.env_root = None
self.camera_root = None
self.camera_accel = 100
self.panda_keymap = {
'mouse1': False
}
self.mouse_previous = Vec3()
self.mouse_direction = Vec3()
init_gui(self, args)
init_packages_tree(self.tree_packages, self.package_manager, self.icons)
init_panda(self, args)
def on_tk_close(self):
self.running = False
def on_packagetree_doubleclick(self, event):
item = self.tree_packages.selection()[0]
if item.lower().endswith(('.glb', '.gltf', '.obj', '.egg')):
for child in self.scene_root.getChildren():
child.removeNode()
mat = Material()
mat.setAmbient((1, 0, 0, 1))
node = self.panda.loader.loadModel(item)
path = node.find('**/+Character')
if not path.isEmpty():
node = Actor(node)
node.reparentTo(self.scene_root)
node.setMaterial(mat)
node.setTwoSided(False)
node.setAttrib(CullFaceAttrib.make(CullFaceAttrib.MCullClockwise))
self.display_scenegraph(node, item)
self.prev = node
def display_scenegraph(self, node, item):
self.tree_scene.delete(*self.tree_scene.get_children())
self.tree_scene.insert('', 'end', '/', text=f' {item}', open=True, image=self.icons.shape_square)
joints = node.getJoints() if getattr(node, 'getJoints', None) else []
if len(joints) > 0:
self.tree_scene.insert('/', 'end', 'joints', text=f' Joints ({len(joints)})', image=self.icons.chart_line_link)
for joint in joints:
self.tree_scene.insert('joints', 'end', f'joint_{joint.getName()}', text=f' {joint.getName()}', image=self.icons.anchor)
self.add_scene_nodes(node, '/')
def add_scene_nodes(self, node, parent):
pn = node.node()
tree_id = f'{pn.getType().getName()} {node.getName()}'
tree_name = tree_id
image = ''
match pn.getType().getName():
case 'ModelRoot':
image = self.icons.chart_organisation
case 'PandaNode':
image = self.icons.anchor
case 'GeomNode':
image = self.icons.shape_flip_horizontal
case 'AnimBundleNode':
bundle = pn.getBundle().getName()
tree_id = f'{tree_id}_{bundle}'
tree_name = f'{bundle}'
image = self.icons.film
case 'Character':
image = self.icons.user
self.tree_scene.insert(parent, 'end', tree_id, text=f' {tree_name}', open=True, image=image)
for child in node.getChildren():
self.add_scene_nodes(child, tree_id)
def mouse1_down(self):
self.panda_keymap['mouse1'] = True
def mouse1_up(self):
self.panda_keymap['mouse1'] = False
def wheel_up(self):
quat = self.panda.camera.getQuat(self.camera_root)
forward = quat.getForward()
self.panda.camera.setPos(self.panda.camera.getPos() + forward)
def wheel_down(self):
quat = self.panda.camera.getQuat(self.camera_root)
forward = quat.getForward()
self.panda.camera.setPos(self.panda.camera.getPos() - forward)
def update_input(self, task):
if self.panda.mouseWatcherNode.hasMouse():
mouse = self.panda.mouseWatcherNode.getMouse()
dt = self.panda.clock.getDt()
self.mouse_direction = Vec3(self.mouse_previous.getX() - mouse.getX(),
self.mouse_previous.getY() - mouse.getY(),
0)
self.mouse_previous = Vec3(mouse.getX(), mouse.getY(), 0)
if self.panda_keymap['mouse1']:
self.camera_root.setH(self.panda.render, self.camera_root.getH() + self.mouse_direction.getX() * self.camera_accel)
self.camera_root.setP(self.panda.render, self.camera_root.getP() + -self.mouse_direction.getY() * self.camera_accel)
return task.cont
def loop(self):
try:
while self.running:
self.gui.update_idletasks()
self.gui.update()
self.panda.taskMgr.step()
except KeyboardInterrupt:
return
def start_explorer(args):
app = PackageExplorer(args)
app.loop()

View File

@ -1,5 +0,0 @@
{
"name": "test-mod",
"version": "1.1-beta",
"type": "include"
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 523 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 464 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 399 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 825 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 749 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 434 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 152 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 892 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 667 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 653 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 537 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 343 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 332 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 641 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 606 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 704 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 403 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 553 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 353 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 610 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 741 B

Binary file not shown.

Binary file not shown.