This commit is contained in:
2025-05-25 23:37:40 +03:00
commit 7f054e2904
79 changed files with 4850 additions and 0 deletions

80
tools/blender_mapping.py Normal file
View File

@@ -0,0 +1,80 @@
import bpy
import os
bl_info = {
"name": "Funnymap export",
"author": "kotofyt",
"version": (1, 0),
"blender": (4, 3, 0),
"location": "File > Export > Funnymap (.map)",
"description": "Export meshes as funnymaps",
"category": "Import-Export",
}
import bpy
import os
from bpy_extras.io_utils import ExportHelper
from bpy.types import Operator
from bpy.props import StringProperty
class ExportFunnymap(Operator, ExportHelper):
"""Export to Funnymap"""
bl_idname = "export_scene.fmap"
bl_label = "Export Funnymap"
filename_ext = ".map"
filter_glob: StringProperty(
default="*.map",
options={'HIDDEN'},
)
def execute(self, context):
return export_my_format(self.filepath)
def export_my_format(filepath):
with open(filepath, 'w') as f:
f.write("{\n")
f.write("\"classname\" \"worldspawn\"\n")
f.write("{\n")
for obj in bpy.context.scene.objects:
if obj.type == 'MESH':
mesh = obj.to_mesh()
mesh.calc_loop_triangles()
uv_layer = mesh.uv_layers.active.data if mesh.uv_layers.active else None
for tri in mesh.loop_triangles:
for loop_index in tri.loops:
vert = mesh.vertices[mesh.loops[loop_index].vertex_index]
world_pos = obj.matrix_world @ vert.co
f.write(f"({world_pos.x:.6f} {world_pos.y:.6f} {world_pos.z:.6f}) ")
for loop_index in tri.loops:
if uv_layer:
uv = uv_layer[loop_index].uv
f.write(f"({uv.x:.6f} {uv.y:.6f}) ")
f.write(f"BRICK0\n");
f.write("}\n")
f.write("}\n")
for obj in bpy.context.scene.objects:
if obj.type == 'LIGHT':
light = obj.data
f.write("{\n")
f.write("\"classname\" \"light\"\n")
f.write(f"\"intensity\" \"{light.energy/128}\"\n")
f.write("}\n")
return {'FINISHED'}
def menu_func_export(self, context):
self.layout.operator(ExportFunnymap.bl_idname, text="Funnymap (.map)")
def register():
bpy.utils.register_class(ExportFunnymap)
bpy.types.TOPBAR_MT_file_export.append(menu_func_export)
def unregister():
bpy.utils.unregister_class(ExportFunnymap)
bpy.types.TOPBAR_MT_file_export.remove(menu_func_export)
if __name__ == "__main__":
register()

53
tools/makepak64.py Normal file
View File

@@ -0,0 +1,53 @@
#
# Copyright (C) 2013 The Tome of Preach
# Copyright (C) 2025 kotofyt
#
# This script implement generator of 64 bit version of pak.
# Basically all size 32 bit offsets and sizes replaced with 64 bit values
#
# Original source code is available at https://tomeofpreach.wordpress.com/2013/06/22/makepak-py/
#
import sys
import struct
import os
#dummy class for stuffing the file headers into
class FileEntry:
pass
#arguments are source directory, then target filename e.g. "pak1.pak"
rootdir = sys.argv[1]
pakfilename = sys.argv[2]
pakfile = open(pakfilename,"wb")
#write a dummy header to start with
pakfile.write(struct.Struct("<8s2q").pack(b"rttpacku",0,0))
#walk the directory recursively, add the files and record the file entries
offset = 24
fileentries = []
for root, subFolders, files in os.walk(rootdir):
for file in files:
entry = FileEntry()
impfilename = os.path.join(root,file)
entry.filename = os.path.relpath(impfilename,rootdir).replace("\\","/")
with open(impfilename, "rb") as importfile:
pakfile.write(importfile.read())
entry.offset = offset
entry.length = importfile.tell()
offset = offset + entry.length
fileentries.append(entry)
tablesize = 0
#after all the file data, write the list of entries
for entry in fileentries:
pakfile.write(struct.Struct("<56s").pack(entry.filename.encode("ascii")))
pakfile.write(struct.Struct("<q").pack(entry.offset))
pakfile.write(struct.Struct("<q").pack(entry.length))
tablesize = tablesize + 72
#return to the header and write the values correctly
pakfile.seek(0)
pakfile.write(struct.Struct("<8s2q").pack(b"rttpacku",offset,tablesize))

112
tools/mapcc.py Normal file
View File

@@ -0,0 +1,112 @@
import sys
import struct
import os
import re
def parse_map(file_path):
with open(file_path, 'r') as f:
lines = f.readlines()
entities = []
stack = []
current_entity = None
current_brush = None
face_pattern = re.compile(r"""
(?:\(\s*(-?\d+(?:\.\d+)?)\s+(-?\d+(?:\.\d+)?)\s+(-?\d+(?:\.\d+)?)\s*\)\s*)
(?:\(\s*(-?\d+(?:\.\d+)?)\s+(-?\d+(?:\.\d+)?)\s+(-?\d+(?:\.\d+)?)\s*\)\s*)
(?:\(\s*(-?\d+(?:\.\d+)?)\s+(-?\d+(?:\.\d+)?)\s+(-?\d+(?:\.\d+)?)\s*\)\s*)
(?:\(\s*(-?\d+(?:\.\d+)?)\s+(-?\d+(?:\.\d+)?)\s*\)\s*)
(?:\(\s*(-?\d+(?:\.\d+)?)\s+(-?\d+(?:\.\d+)?)\s*\)\s*)
(?:\(\s*(-?\d+(?:\.\d+)?)\s+(-?\d+(?:\.\d+)?)\s*\)\s*)
(\S+)
""", re.VERBOSE)
for line in lines:
line = line.strip()
if not line or line.startswith('//'):
continue
if line == '{':
stack.append('{')
if current_entity is None:
current_entity = {'classname': None, 'keyvalues': {}, 'brushes': []}
elif current_brush is None:
current_brush = []
continue
if line == '}':
if current_brush is not None:
current_entity['brushes'] = current_brush
current_brush = None
else:
entities.append(current_entity)
current_entity = None
stack.pop()
continue
if current_brush is not None:
m = face_pattern.match(line)
if m:
points = [
(float(m.group(1)), float(m.group(2)), float(m.group(3))),
(float(m.group(4)), float(m.group(5)), float(m.group(6))),
(float(m.group(7)), float(m.group(8)), float(m.group(9))),
]
uvs = [
(float(m.group(10)), float(m.group(11))),
(float(m.group(12)), float(m.group(13))),
(float(m.group(14)), float(m.group(15))),
]
texture = m.group(16)
face = {
'points': points,
'uvs': uvs,
'texture': texture,
}
current_brush.append(face)
else:
print(f"Warning: Could not parse face line: {line}")
continue
if current_entity is not None:
if line.startswith('"'):
parts = re.findall(r'"([^"]*)"', line)
if len(parts) == 2:
key, value = parts
if key == "classname":
current_entity['classname'] = value
else:
current_entity['keyvalues'][key] = value
else:
print(f"Warning: Unexpected line in entity: {line}")
return entities
if (len(sys.argv)==1):
print("mapcc.py is a rtt's .map to .fmap converter");
print("key changes:");
print("- uses triangles instead of brushes");
print("- uses uv coordinates instead of brushes");
print("use better map editor instead of this");
exit(0)
entities = parse_map(sys.argv[1])
pakfilename = sys.argv[2]
pakfile = open(pakfilename,"wb")
pakfile.write(struct.Struct("<8sI").pack(b"rttfmapc",len(entities)))
for i, e in enumerate(entities):
pakfile.write(struct.Struct(f"<{len(e['classname'])+1}s").pack(str(e['classname']).encode("ascii")))
pakfile.write(struct.Struct("<I").pack(len(e['brushes'])))
pakfile.write(struct.Struct("<I").pack(len(e['keyvalues'])))
for k, v in e['keyvalues'].items():
pakfile.write(struct.Struct(f"<{len(k)+1}s").pack(k.encode("ascii")))
pakfile.write(struct.Struct(f"<{len(v)+1}s").pack(v.encode("ascii")))
for k, face in enumerate(e['brushes']):
# the fuck
for i in range(9):
pakfile.write(struct.Struct(f"<f").pack(float(face['points'][int(i/3)][i%3])))
for i in range(6):
pakfile.write(struct.Struct(f"<f").pack(float(face['uvs'][int(i//2)][i%2])))
pakfile.write(struct.Struct(f"<{len(face['texture'])+1}s").pack(face['texture'].encode("ascii")))