├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── addons └── vmflib │ ├── LICENSE │ ├── README.md │ ├── VMF.gd │ ├── VMFRoot.gd │ ├── examples │ ├── Example1.gd │ ├── Example1.tscn │ ├── Example2.gd │ └── Example2.tscn │ ├── icon │ ├── .gdignore │ └── icon.png │ ├── plugin.cfg │ └── vmflib_plugin.gd ├── icon.svg ├── icon.svg.import └── project.godot /.gitattributes: -------------------------------------------------------------------------------- 1 | # Normalize EOL for all files that Git considers text files. 2 | * text=auto eol=lf 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Godot 4+ specific ignores 2 | .godot/ 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021, Brendan Lewis 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 2. Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 14 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 17 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 18 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 19 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 20 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 21 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 22 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vmflib-godot 2 | [![Discord](https://img.shields.io/discord/678074864346857482?logo=discord&style=flat-square)](https://discord.gg/ASgHfkX/) 3 | 4 | A port of vmflib2 to the Godot Game Engine. 5 | 6 | ## Information 7 | 8 | This addon allows you to create a Source Engine VMF file, and export to a BSP. 9 | It is targeted towards Portal 2, although other games will work with it. 10 | This was made for my "Godot Puzzlemaker" level editor, and I will update it as necessary. 11 | It is targeted toward desktop platforms, but should theoretically work on any platform Godot does. 12 | 13 | The code is an improved version of [https://github.com/Trainzack/vmflib2](https://github.com/Trainzack/vmflib2), which is under the BSD 2-Clause "Simplified" License. 14 | Thus, this code is as well. See more info at LICENSE. 15 | 16 | This addon is still in development. The overall foundation is in place, it is just a matter of adding more entity and texture listings, and adding more features. 17 | 18 | ## Installation 19 | 20 | 1. Download this repository as a ZIP. 21 | 2. Unzip the downloaded file anywhere. 22 | 3. Copy the `addons/` folder to the root folder of your project. It won't overwrite any addons you currently have installed. 23 | 4. In Project Settings, under the Plugins tab select the checkbox to enable the `vmflib` plugin. 24 | 5. In Project Settings, under the AutoLoad tab add `addons/vmflib/VMFDataManager.gd` as a singleton. 25 | **Make sure it is exactly called VMFDataManager, Godot may make some letters lowercase.** 26 | 27 | ## Usage 28 | 29 | In the script to create a level, write: 30 | 31 | `var vmf = VMF.new(VMF.GAMES.GameToExportToHere)` 32 | 33 | This object is how you add brushes, entities, and export to VMF and/or BSP. 34 | 35 | To add a brush, you can write something like this. 36 | Note that every Vector3 argument uses a +Y = UP coordinate system, where the exported VMF uses a +Z = UP coordinate system. 37 | This is because of personal preference. 38 | 39 | `vmf.add_solid(VMF.Block.new(Vector3(0, 0, 0), Vector3(64, 64, 64), vmf.COMMON_MATERIALS.DEV_MEASUREWALL01A).brush)` 40 | 41 | In the Block constructor, the first argument is the coordinates of the center of the block. 42 | The second argument is the size of the block. 43 | The third argument is the texture to use for each block face (defaults to "TOOLS/TOOLSNODRAW"). 44 | 45 | To change the texture of one face, add `.set_material_face(material: String, face: Vector3)` before `.brush`. 46 | This function modifies the block object it is called on, but it also returns the block object, so it can be chained. 47 | Use `Vector3.UP`, `Vector3.DOWN`, `Vector3.LEFT`, etc. to select the face to change. 48 | 49 | Adding an entity is slightly different. Here is an example: 50 | 51 | `var info_player_start = VMF.Entities.Common.InfoPlayerStartEntity.new(vmf, Vector3(0, 64, 0))` 52 | 53 | Each type of entity is an instance of a separate helper class. 54 | It adds itself to the VMF object passed to the constructor automatically. 55 | Depending on the type of entity, there are different arguments. 56 | 57 | Finally, run `vmf.write_vmf(file_path_here: String)` to save the VMF to the specified path, or save the value of `str(vmf)` for use in scripting. 58 | -------------------------------------------------------------------------------- /addons/vmflib/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021, Brendan Lewis 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 2. Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 14 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 17 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 18 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 19 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 20 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 21 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 22 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /addons/vmflib/README.md: -------------------------------------------------------------------------------- 1 | # vmflib-godot 2 | A port of vmflib2 to the Godot Game Engine. 3 | 4 | ## Information 5 | 6 | This addon allows you to create a Source Engine VMF file, and export to a BSP. 7 | It is targeted towards Portal 2, although other games will work with it. 8 | This was made for my "Godot Puzzlemaker" level editor, and I will update it as necessary. 9 | It is targeted toward desktop platforms, but should theoretically work on any platform Godot does. 10 | 11 | The code is an improved version of [https://github.com/Trainzack/vmflib2](https://github.com/Trainzack/vmflib2), which is under the BSD 2-Clause "Simplified" License. 12 | Thus, this code is as well. See more info at LICENSE. 13 | 14 | This addon is still in development. The overall foundation is in place, it is just a matter of adding more entity and texture listings, and adding more features. 15 | 16 | ## Installation 17 | 18 | 1. Download this repository as a ZIP. 19 | 2. Unzip the downloaded file anywhere. 20 | 3. Copy the `addons/` folder to the root folder of your project. It won't overwrite any addons you currently have installed. 21 | 4. In Project Settings, under the Plugins tab select the checkbox to enable the `vmflib` plugin. 22 | 5. In Project Settings, under the AutoLoad tab add `addons/vmflib/VMFDataManager.gd` as a singleton. 23 | **Make sure it is exactly called VMFDataManager, Godot may make some letters lowercase.** 24 | 25 | ## Usage 26 | 27 | In the script to create a level, write: 28 | 29 | `var vmf = VMF.new(VMF.GAMES.GameToExportToHere)` 30 | 31 | This object is how you add brushes, entities, and export to VMF and/or BSP. 32 | 33 | To add a brush, you can write something like this. 34 | Note that every Vector3 argument uses a +Y = UP coordinate system, where the exported VMF uses a +Z = UP coordinate system. 35 | This is because of personal preference. 36 | 37 | `vmf.add_solid(VMF.Block.new(Vector3(0, 0, 0), Vector3(64, 64, 64), vmf.COMMON_MATERIALS.DEV_MEASUREWALL01A).brush)` 38 | 39 | In the Block constructor, the first argument is the coordinates of the center of the block. 40 | The second argument is the size of the block. 41 | The third argument is the texture to use for each block face (defaults to "TOOLS/TOOLSNODRAW"). 42 | 43 | To change the texture of one face, add `.set_material_face(material: String, face: Vector3)` before `.brush`. 44 | This function modifies the block object it is called on, but it also returns the block object, so it can be chained. 45 | Use `Vector3.UP`, `Vector3.DOWN`, `Vector3.LEFT`, etc. to select the face to change. 46 | 47 | Adding an entity is slightly different. Here is an example: 48 | 49 | `var info_player_start = VMF.Entities.Common.InfoPlayerStartEntity.new(vmf, Vector3(0, 64, 0))` 50 | 51 | Each type of entity is an instance of a separate helper class. 52 | It adds itself to the VMF object passed to the constructor automatically. 53 | Depending on the type of entity, there are different arguments. 54 | 55 | Finally, run `vmf.write_vmf(file_path_here: String)` to save the VMF to the specified path, or save the value of `str(vmf)` for use in scripting. 56 | -------------------------------------------------------------------------------- /addons/vmflib/VMF.gd: -------------------------------------------------------------------------------- 1 | extends "res://addons/vmflib/VMFRoot.gd" 2 | class_name VMF 3 | 4 | 5 | var versioninfo: VersionInfo 6 | var visgroups: VisGroups 7 | var viewsettings: ViewSettings 8 | var world: VWorld 9 | var cameras: Cameras 10 | var cordons: Cordons 11 | 12 | var game_path: String 13 | 14 | 15 | enum GAMES { 16 | PORTAL_2 17 | } 18 | 19 | const DEFAULT_MATERIAL = "TOOLS/TOOLSNODRAW" 20 | 21 | 22 | func _init(game: int = GAMES.PORTAL_2) -> void: 23 | super("") 24 | 25 | self.versioninfo = VersionInfo.new() 26 | self.visgroups = VisGroups.new() 27 | self.viewsettings = ViewSettings.new() 28 | self.cameras = Cameras.new() 29 | self.cordons = Cordons.new() 30 | 31 | self.children.append(self.versioninfo) 32 | self.children.append(self.visgroups) 33 | self.children.append(self.viewsettings) 34 | self.children.append(self.cameras) 35 | self.children.append(self.cordons) 36 | self.world = VWorld.new(self, game) # Automatically added to children 37 | 38 | func add_solid(block: Block) -> VMF: 39 | self.world.children.append(block.brush) 40 | return self 41 | 42 | func add_solids(blocks: Array[Block]) -> VMF: 43 | for b in blocks: 44 | self.add_solid(b) 45 | return self 46 | 47 | func add_block(origin: Vector3 = Vector3(), dimensions: Vector3 = Vector3(64, 64, 64), material: String = DEFAULT_MATERIAL) -> VMF: 48 | return self.add_solid(Block.new(self, origin, dimensions, material)) 49 | 50 | var entity_count := 0 51 | func allocEntityID() -> int: 52 | self.entity_count += 1 53 | return self.entity_count - 1 54 | 55 | var world_count := 0 56 | func allocWorldID() -> int: 57 | self.world_count += 1 58 | return self.world_count - 1 59 | 60 | var solid_count := 0 61 | func allocSolidID() -> int: 62 | self.solid_count += 1 63 | return self.solid_count - 1 64 | 65 | var side_count := 0 66 | func allocSideID() -> int: 67 | self.side_count += 1 68 | return self.side_count - 1 69 | 70 | var group_count := 0 71 | func allocGroupID() -> int: 72 | self.group_count += 1 73 | return self.group_count - 1 74 | 75 | var visgroup_count := 0 76 | func allocVisGroupID() -> int: 77 | self.visgroup_count += 1 78 | return self.visgroup_count - 1 79 | 80 | func write_to_file(filename: String = "user://output.vmf") -> void: 81 | var f: FileAccess = FileAccess.open(filename, FileAccess.WRITE) 82 | f.store_string(self.getAsStr()) 83 | f.close() 84 | 85 | func _to_string() -> String: 86 | return self.getAsStr() 87 | 88 | 89 | # -v- HELPER CLASSES -v- # 90 | 91 | class Vertex: 92 | var x: int 93 | var y: int 94 | var z: int 95 | var parenthesis: bool 96 | 97 | func _init(x: int = 0, y: int = 0, z: int = 0, show_parenthesis: bool = true) -> void: 98 | self.x = x 99 | self.y = y 100 | self.z = z 101 | self.parenthesis = show_parenthesis 102 | 103 | func setParenthesis(enabled: bool): 104 | self.parenthesis = enabled 105 | 106 | func vec3() -> Vector3: 107 | return Vector3(self.x, self.y, self.z) 108 | 109 | func getAsStr() -> String: 110 | if parenthesis: 111 | return "(%d %d %d)" % [self.x, self.y, self.z] 112 | else: 113 | return "%d %d %d" % [self.x, self.y, self.z] 114 | 115 | func _to_string() -> String: 116 | return self.getAsStr() 117 | 118 | func add(other: Vertex): 119 | return Vertex.new(self.x + other.x, self.y + other.y, self.z + other.z) 120 | 121 | func subtract(other: Vertex) -> Vertex: 122 | return Vertex.new(self.x - other.x, self.y - other.y, self.z - other.z) 123 | 124 | func multiply(other: int) -> Vertex: 125 | return Vertex.new(self.x * other, self.y * other, self.z * other) 126 | 127 | 128 | class Axis: 129 | var x: int 130 | var y: int 131 | var z: int 132 | var tex_shift: int 133 | var tex_scale: float 134 | 135 | func _init(origin: Vector3i, tex_shift: int = 0, tex_scale: float = 0.25) -> void: 136 | self.x = origin.x 137 | self.y = origin.y 138 | self.z = origin.z 139 | self.tex_shift = tex_shift 140 | self.tex_scale = tex_scale 141 | 142 | func getAsStr() -> String: 143 | return "[%d %d %d %d] %f" % [self.x, self.y, self.z, self.tex_shift, self.tex_scale] 144 | 145 | func _to_string() -> String: 146 | return self.getAsStr() 147 | 148 | 149 | class VColor: 150 | var r: int 151 | var g: int 152 | var b: int 153 | var a: int 154 | 155 | func _init(r: int, g: int, b: int, a: int = -1) -> void: 156 | self.r = r 157 | self.g = g 158 | self.b = b 159 | self.a = a 160 | 161 | func getAsStr() -> String: 162 | if a < 0: 163 | return "%d %d %d" % [r, g, b] 164 | else: 165 | return "%d %d %d %d" % [r, g, b, a] 166 | 167 | func _to_string() -> String: 168 | return self.getAsStr() 169 | 170 | 171 | class VPlane: 172 | var v0: Vertex: 173 | set(v): 174 | v0 = v 175 | v0.setParenthesis(true) 176 | self.recalculate_normal() 177 | var v1: Vertex: 178 | set(v): 179 | v1 = v 180 | v1.setParenthesis(true) 181 | self.recalculate_normal() 182 | var v2: Vertex: 183 | set(v): 184 | v2 = v 185 | v2.setParenthesis(true) 186 | self.recalculate_normal() 187 | var normal: Vector3 188 | 189 | func _init(v0: Vertex = Vertex.new(), v1: Vertex = Vertex.new(), v2: Vertex = Vertex.new()) -> void: 190 | self.v0 = v0 191 | self.v1 = v1 192 | self.v2 = v2 193 | 194 | func recalculate_normal() -> void: 195 | if v0 != null and v1 != null and v2 != null: 196 | self.normal = v1.vec3() - v0.vec3() 197 | self.normal = normal.cross(v2.vec3() - v0.vec3()) 198 | self.normal = normal.normalized() 199 | 200 | func get_normal() -> Vector3: 201 | if (self.normal != Vector3.ZERO): 202 | return self.normal 203 | self.recalculate_normal() 204 | return self.normal 205 | 206 | # https://github.com/LogicAndTrick/sledge-formats/blob/master/Sledge.Formats/NumericsExtensions.cs#L113 207 | func get_best_axes() -> Array[Vector3i]: 208 | const baseaxis := [ 209 | Vector3i(0, 0, 1), Vector3i(1, 0, 0), Vector3i(0, -1, 0), 210 | Vector3i(0, 0, -1), Vector3i(1, 0, 0), Vector3i(0, -1, 0), 211 | Vector3i(1, 0, 0), Vector3i(0, 1, 0), Vector3i(0, 0, -1), 212 | Vector3i(-1, 0, 0), Vector3i(0, 1, 0), Vector3i(0, 0, -1), 213 | Vector3i(0, 1, 0), Vector3i(1, 0, 0), Vector3i(0, 0, -1), 214 | Vector3i(0, -1, 0), Vector3i(1, 0, 0), Vector3i(0, 0, -1) 215 | ] 216 | 217 | var best := 0.0; 218 | var bestaxis := 0; 219 | 220 | for i in range(6): 221 | var dot = self.get_normal().dot(baseaxis[i * 3]); 222 | if not(dot > best): continue; 223 | 224 | best = dot; 225 | bestaxis = i; 226 | 227 | return [baseaxis[bestaxis * 3 + 1], baseaxis[bestaxis * 3 + 2], baseaxis[bestaxis * 3]] 228 | 229 | func get_uaxis() -> Axis: 230 | return Axis.new(self.get_best_axes()[0]) 231 | 232 | func get_vaxis() -> Axis: 233 | return Axis.new(self.get_best_axes()[1]) 234 | 235 | func get_axes_normal() -> Vector3i: 236 | return self.get_best_axes()[2] 237 | 238 | func getAsStr() -> String: 239 | return "%s %s %s" % [str(v0), str(v1), str(v2)] 240 | 241 | func _to_string() -> String: 242 | return self.getAsStr() 243 | 244 | 245 | class Output: 246 | var event: String 247 | var target: String 248 | var input: String 249 | var parameter: String 250 | var delay: int 251 | var times_to_fire: int 252 | 253 | func _init(event: String, target: String, input: String, parameter: String = "", delay: int = 0, times_to_fire: int = -1) -> void: 254 | self.event = event 255 | self.target = target 256 | self.input = input 257 | self.parameter = parameter 258 | self.delay = delay 259 | self.times_to_fire = times_to_fire 260 | 261 | func getAsStr() -> String: 262 | return "\"%s\" \"%s,%s,%s,%d,%d\"" % [self.event, self.target, self.input, self.parameter, self.delay, self.times_to_fire] 263 | 264 | func _to_string() -> String: 265 | return self.getAsStr() 266 | 267 | 268 | class VersionInfo extends "res://addons/vmflib/VMFRoot.gd": 269 | var editorversion = 400 270 | var editorbuild = 8997 271 | var mapversion = 0 272 | var formatversion = 100 273 | var prefab = 0 274 | 275 | func _init() -> void: 276 | super("versioninfo") 277 | self.auto_properties.append("editorversion") 278 | self.auto_properties.append("editorbuild") 279 | self.auto_properties.append("mapversion") 280 | self.auto_properties.append("formatversion") 281 | self.auto_properties.append("prefab") 282 | 283 | 284 | class VisGroups extends "res://addons/vmflib/VMFRoot.gd": 285 | func _init() -> void: 286 | super("visgroups") 287 | 288 | 289 | class ViewSettings extends "res://addons/vmflib/VMFRoot.gd": 290 | var bSnapToGrid: bool 291 | var bShowGrid: bool 292 | var bShowLogicalGrid: bool 293 | var nGridSpacing: int 294 | var bShow3DGrid: bool 295 | 296 | func _init(grid_snap: bool = true, grid_visible: bool = true, grid_spacing: int = 64, grid_3d_visible: bool = false) -> void: 297 | super("viewsettings") 298 | self.bSnapToGrid = grid_snap 299 | self.bShowGrid = grid_visible 300 | self.bShowLogicalGrid = false 301 | self.nGridSpacing = grid_spacing 302 | self.bShow3DGrid = grid_3d_visible 303 | self.auto_properties.append("bSnapToGrid") 304 | self.auto_properties.append("bShowGrid") 305 | self.auto_properties.append("bShowLogicalGrid") 306 | self.auto_properties.append("nGridSpacing") 307 | self.auto_properties.append("bShow3DGrid") 308 | 309 | 310 | class Cameras extends "res://addons/vmflib/VMFRoot.gd": 311 | var activecamera := -1 312 | 313 | func _init() -> void: 314 | super("cameras") 315 | self.auto_properties.append("activecamera") 316 | 317 | 318 | class Cordons extends "res://addons/vmflib/VMFRoot.gd": 319 | var active := false 320 | 321 | func _init() -> void: 322 | super("cordons") 323 | self.auto_properties.append("active") 324 | 325 | 326 | class Entity extends "res://addons/vmflib/VMFRoot.gd": 327 | var classname: String 328 | var connections: Connections 329 | 330 | func _init(vmf: VMF, className: String, super_name: String = "entity") -> void: 331 | super(super_name) 332 | self.classname = className 333 | self.connections = null 334 | self.properties['id'] = vmf.allocEntityID() 335 | self.properties['classname'] = self.classname 336 | vmf.children.append(self) 337 | 338 | func add_output(output: Output) -> void: 339 | if self.connections == null: 340 | self.connections = Connections.new() 341 | self.children.append(self.connections) 342 | self.connections.children.append(output) 343 | 344 | func add_outputs(outputs: Array[Output]) -> void: 345 | for o in outputs: 346 | self.add_output(o) 347 | 348 | 349 | class Connections extends "res://addons/vmflib/VMFRoot.gd": 350 | func _init() -> void: 351 | super("connections") 352 | 353 | func getAsStr(tab_level: int = -1) -> String: 354 | var string: String = "" 355 | var tab_prefix: String = "" 356 | for i in range(tab_level): 357 | tab_prefix += "\t" 358 | var tab_prefix_inner: String = tab_prefix + "\t" 359 | if (self.vmf_class_name): 360 | string += tab_prefix + self.vmf_class_name + "\n" 361 | string += tab_prefix + "{\n" 362 | for output in self.children: 363 | string += tab_prefix_inner + output + "\n" 364 | if (self.vmf_class_name): 365 | string += tab_prefix + "}\n" 366 | return string 367 | 368 | func _to_string() -> String: 369 | return self.getAsStr() 370 | 371 | 372 | class VWorld extends Entity: 373 | var skyname: String 374 | var detailmaterial: String 375 | var detailvbsp: String 376 | var maxblobcount: int 377 | var maxpropscreenwidth: int 378 | var mapversion := 0 379 | 380 | func _init(vmf: VMF, game: int) -> void: 381 | super(vmf, "worldspawn", "world") 382 | self.auto_properties.append("mapversion") 383 | match game: 384 | GAMES.PORTAL_2: 385 | self.skyname = "sky_black_nofog" 386 | self.detailmaterial = "detail/detailsprites" 387 | self.detailvbsp = "detail.vbsp" 388 | self.maxblobcount = 250 389 | self.maxpropscreenwidth = -1 390 | self.auto_properties.append("skyname") 391 | self.auto_properties.append("detailmaterial") 392 | self.auto_properties.append("detailvbsp") 393 | self.auto_properties.append("maxblobcount") 394 | self.auto_properties.append("maxpropscreenwidth") 395 | _: 396 | assert(false, "Must be a valid game") 397 | self.properties['id'] = vmf.allocWorldID() 398 | 399 | 400 | class Solid extends "res://addons/vmflib/VMFRoot.gd": 401 | func _init(vmf: VMF) -> void: 402 | super("solid") 403 | self.properties['id'] = vmf.allocSolidID() 404 | 405 | 406 | class Side extends "res://addons/vmflib/VMFRoot.gd": 407 | var plane: VPlane 408 | var material: String 409 | var rotation := 0 410 | var lightmapscale := 16 411 | var smoothing_groups := 0 412 | var uaxis: Axis: 413 | get: 414 | return self.plane.get_uaxis() 415 | var vaxis: Axis: 416 | get: 417 | return self.plane.get_vaxis() 418 | 419 | func _init(vmf: VMF, plane: VPlane = VPlane.new(), material: String = DEFAULT_MATERIAL) -> void: 420 | super("side") 421 | self.plane = plane 422 | self.material = material 423 | self.auto_properties.append("plane") 424 | self.auto_properties.append("material") 425 | self.auto_properties.append("uaxis") 426 | self.auto_properties.append("vaxis") 427 | self.auto_properties.append("rotation") 428 | self.auto_properties.append("lightmapscale") 429 | self.auto_properties.append("smoothing_groups") 430 | self.properties['id'] = vmf.allocSideID() 431 | 432 | 433 | class Group extends "res://addons/vmflib/VMFRoot.gd": 434 | func _init(vmf: VMF) -> void: 435 | super("group") 436 | self.properties['id'] = vmf.allocGroupID() 437 | 438 | # DispInfo ; Offsets ; OffestNormals ; TriangleTags ; AllowedVerts 439 | # not included 440 | 441 | class Normals extends "res://addons/vmflib/VMFRoot.gd": 442 | func _init(power, values) -> void: 443 | super("normals") 444 | for i in range(pow(2, power) + 1): 445 | var row_string = "" 446 | for vert in values[i]: 447 | row_string += str(vert.x) + " " + str(vert.y) + " " + str(vert.z) 448 | self.properties["row" + str(i)] = row_string 449 | 450 | 451 | class Distances extends "res://addons/vmflib/VMFRoot.gd": 452 | var values: Array[int] 453 | 454 | func _init(power: int, values: Array[int]) -> void: 455 | super("distances") 456 | self.values = values 457 | for i in range(pow(2, power) + 1): 458 | var row_string = "" 459 | for distance in values[i]: 460 | row_string += str(distance) + " " 461 | self.properties["row" + str(i)] = row_string.strip_edges(false, true) 462 | 463 | 464 | class Alphas extends "res://addons/vmflib/VMFRoot.gd": 465 | func _init(power: int, values: Array[int] = []) -> void: 466 | super("alphas") 467 | if len(values) > 0: 468 | for i in range(pow(2, power) + 1): 469 | var row_string = "" 470 | for distance in values[i]: 471 | row_string += str(distance) + " " 472 | self.properties["row" + str(i)] = row_string.strip_edges(false, true) 473 | 474 | 475 | class Block: 476 | var origin: Vertex 477 | var dimensions: Array 478 | var brush: Solid 479 | 480 | func _init(vmf: VMF, origin: Vector3 = Vector3(), dimensions: Vector3 = Vector3(64, 64, 64), material: String = DEFAULT_MATERIAL) -> void: 481 | self.origin = Vertex.new(origin.x, origin.z, origin.y) 482 | self.dimensions = [dimensions.x, dimensions.z, dimensions.y] 483 | self.brush = Solid.new(vmf) 484 | for i in range(6): 485 | self.brush.children.append(Side.new(vmf, VPlane.new(), material)) 486 | self.update_sides() 487 | self.set_material(material) 488 | 489 | func update_sides() -> void: 490 | var x = self.origin.x 491 | var y = self.origin.y 492 | var z = self.origin.z 493 | var w = self.dimensions[0] 494 | var l = self.dimensions[1] 495 | var h = self.dimensions[2] 496 | var a = w / 2 497 | var b = l / 2 498 | var c = h / 2 499 | self.brush.children[0].plane = VPlane.new( 500 | Vertex.new(x - a, y + b, z + c), 501 | Vertex.new(x + a, y + b, z + c), 502 | Vertex.new(x + a, y - b, z + c)) 503 | self.brush.children[1].plane = VPlane.new( 504 | Vertex.new(x - a, y - b, z - c), 505 | Vertex.new(x + a, y - b, z - c), 506 | Vertex.new(x + a, y + b, z - c)) 507 | self.brush.children[2].plane = VPlane.new( 508 | Vertex.new(x - a, y + b, z + c), 509 | Vertex.new(x - a, y - b, z + c), 510 | Vertex.new(x - a, y - b, z - c)) 511 | self.brush.children[3].plane = VPlane.new( 512 | Vertex.new(x + a, y + b, z - c), 513 | Vertex.new(x + a, y - b, z - c), 514 | Vertex.new(x + a, y - b, z + c)) 515 | self.brush.children[4].plane = VPlane.new( 516 | Vertex.new(x + a, y + b, z + c), 517 | Vertex.new(x - a, y + b, z + c), 518 | Vertex.new(x - a, y + b, z - c)) 519 | self.brush.children[5].plane = VPlane.new( 520 | Vertex.new(x + a, y - b, z - c), 521 | Vertex.new(x - a, y - b, z - c), 522 | Vertex.new(x - a, y - b, z + c)) 523 | 524 | func set_material(material: String) -> void: 525 | for side in self.brush.children: 526 | side.material = material 527 | 528 | func get_side(side: Vector3) -> Side: 529 | match side: 530 | Vector3.UP: 531 | return self.brush.children[0] 532 | Vector3.DOWN: 533 | return self.brush.children[1] 534 | Vector3.BACK: 535 | return self.brush.children[2] 536 | Vector3.FORWARD: 537 | return self.brush.children[3] 538 | Vector3.LEFT: 539 | return self.brush.children[4] 540 | Vector3.RIGHT: 541 | return self.brush.children[5] 542 | _: 543 | assert(false, "Vector3 passed must be Vector3.UP, Vector3.DOWN, etc.") 544 | return self.brush.children[0] 545 | 546 | func getAsStr(tab_level: int = -1) -> String: 547 | return self.brush.getAsStr(tab_level) 548 | 549 | func _to_string() -> String: 550 | return self.getAsStr() 551 | 552 | # Other classes in tools.py excluded 553 | 554 | 555 | # -v- ENTITIES -v- # 556 | 557 | class InfoPlayerStartEntity extends Entity: 558 | var origin: Vertex 559 | var angles: Vertex 560 | 561 | func _init(vmf: VMF, origin: Vector3 = Vector3(), angles: Vector3 = Vector3()) -> void: 562 | super(vmf, "info_player_start") 563 | self.origin = Vertex.new(origin.x, origin.z, origin.y) 564 | self.origin.setParenthesis(false) 565 | self.angles = Vertex.new(angles.x, angles.z, angles.y) 566 | self.angles.setParenthesis(false) 567 | self.auto_properties.append("origin") 568 | self.auto_properties.append("angles") 569 | 570 | class LightEntity extends Entity: 571 | var origin: Vertex 572 | var targetname: String 573 | var _light: VColor 574 | var _lightHDR: VColor 575 | var _lightscaleHDR: float 576 | var style: int 577 | var pattern: String 578 | var _constant_attn: bool 579 | var _linear_attn: bool 580 | var _quadratic_attn: bool 581 | var _fifty_percent_distance: bool 582 | var _zero_percent_distance: bool 583 | var _hardfalloff: int 584 | var target: String 585 | var _distance: int 586 | 587 | func _init(vmf: VMF, origin: Vector3 = Vector3(), \ 588 | targetname: String = "", _light: VColor = VColor.new(255,255,255,200), \ 589 | _lightHDR: VColor = VColor.new(-1,-1,-1,1), _lightscaleHDR: float = 1, \ 590 | style: int = 0, pattern: String = "", _constant_attn: bool = false, \ 591 | _linear_attn: bool = true, _quadratic_attn: bool = false, \ 592 | _fifty_percent_distance: bool = false, _zero_percent_distance: bool = false, \ 593 | _hardfalloff: int = 0, target: String = "", _distance: int = 0) -> void: 594 | super(vmf, "light") 595 | self.origin = Vertex.new(origin.x, origin.z, origin.y) 596 | self.origin.setParenthesis(false) 597 | self.targetname = targetname 598 | self._light = _light 599 | self._lightHDR = _lightHDR 600 | self._lightscaleHDR = _lightscaleHDR 601 | self.style = style 602 | self.pattern = pattern 603 | self._constant_attn = _constant_attn 604 | self._linear_attn = _linear_attn 605 | self._quadratic_attn = _quadratic_attn 606 | self._fifty_percent_distance = _fifty_percent_distance 607 | self._zero_percent_distance = _zero_percent_distance 608 | self._hardfalloff = _hardfalloff 609 | self.target = target 610 | self._distance = _distance 611 | for prop in [ 612 | "origin", "targetname", \ 613 | "_light", "_lightHDR", \ 614 | "_lightscaleHDR", "style",\ 615 | "pattern", "_constant_attn",\ 616 | "_linear_attn", "_quadratic_attn",\ 617 | "_fifty_percent_distance", "_zero_percent_distance",\ 618 | "_hardfalloff", "target",\ 619 | "spawnflags", "_distance" 620 | ]: 621 | self.auto_properties.append(prop) 622 | -------------------------------------------------------------------------------- /addons/vmflib/VMFRoot.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | 3 | 4 | var vmf_class_name: String = "UntitledClass" 5 | var properties: Dictionary 6 | var auto_properties: Array[String] 7 | var children: Array 8 | 9 | 10 | func _init(className: String) -> void: 11 | self.vmf_class_name = className 12 | self.properties = {} 13 | self.auto_properties = [] 14 | self.children = [] 15 | 16 | func getAsStr(tab_level: int = -1) -> String: 17 | var string: String = "" 18 | var tab_prefix: String = "" 19 | for i in range(tab_level): 20 | tab_prefix += "\t" 21 | var tab_prefix_inner = tab_prefix + "\t" 22 | if self.vmf_class_name != "": 23 | string += tab_prefix + self.vmf_class_name + "\n" 24 | string += tab_prefix + "{\n" 25 | for attr_name in self.auto_properties: 26 | var value = self.get(attr_name) 27 | if !(value == null): 28 | if value is bool: 29 | string += tab_prefix_inner + "\"" + str(attr_name) + "\" \"" + str(int(value)) + "\"\n" 30 | else: 31 | string += tab_prefix_inner + "\"" + str(attr_name) + "\" \"" + str(value) + "\"\n" 32 | for key in self.properties.keys(): 33 | string += tab_prefix_inner + "\"" + str(key) + "\" " + "\"" + str(self.properties[key]) + "\"\n" 34 | for child in self.children: 35 | string += child.getAsStr(tab_level + 1) 36 | if self.vmf_class_name != "": 37 | string += tab_prefix + "}\n" 38 | return string 39 | 40 | func _to_string() -> String: 41 | return self.getAsStr() 42 | -------------------------------------------------------------------------------- /addons/vmflib/examples/Example1.gd: -------------------------------------------------------------------------------- 1 | extends Button 2 | 3 | 4 | func _on_pressed() -> void: 5 | var vmf = VMF.new() 6 | 7 | vmf \ 8 | .add_block(Vector3(0, -32, 0), Vector3(512, 64, 512), "DEV/DEV_MEASUREWALL01A") \ 9 | .add_block(Vector3(0, 256 + 32, 0), Vector3(512, 64, 512), "DEV/DEV_MEASUREWALL01A") \ 10 | .add_block(Vector3(0, 128, 256 + 32), Vector3(512, 256, 64), "DEV/DEV_MEASUREWALL01A") \ 11 | .add_block(Vector3(0, 128, -256 - 32), Vector3(512, 256, 64), "DEV/DEV_MEASUREWALL01A") \ 12 | .add_block(Vector3(256 + 32, 128, 0), Vector3(64, 256, 512), "DEV/DEV_MEASUREWALL01A") \ 13 | .add_block(Vector3(-256 - 32, 128, 0), Vector3(64, 256, 512), "DEV/DEV_MEASUREWALL01A") 14 | 15 | var info_player_start = VMF.InfoPlayerStartEntity.new(vmf) 16 | var light = VMF.LightEntity.new(vmf, Vector3(0,128,0)) 17 | 18 | vmf.write_to_file("user://output.vmf") 19 | 20 | get_parent().get_node("Label2").show() 21 | get_parent().get_node("Button2").show() 22 | 23 | 24 | func _on_Button2_pressed() -> void: 25 | OS.shell_open(OS.get_user_data_dir()) 26 | -------------------------------------------------------------------------------- /addons/vmflib/examples/Example1.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=3 uid="uid://c4fahpdsdbn6j"] 2 | 3 | [ext_resource type="Script" path="res://addons/vmflib/examples/Example1.gd" id="1"] 4 | 5 | [node name="Example1" type="Control"] 6 | layout_mode = 3 7 | anchors_preset = 15 8 | anchor_right = 1.0 9 | anchor_bottom = 1.0 10 | grow_horizontal = 2 11 | grow_vertical = 2 12 | 13 | [node name="CenterContainer" type="CenterContainer" parent="."] 14 | layout_mode = 0 15 | anchor_right = 1.0 16 | anchor_bottom = 1.0 17 | 18 | [node name="VBoxContainer" type="VBoxContainer" parent="CenterContainer"] 19 | layout_mode = 2 20 | 21 | [node name="Label" type="Label" parent="CenterContainer/VBoxContainer"] 22 | layout_mode = 2 23 | text = "Generate a simple level:" 24 | 25 | [node name="Button" type="Button" parent="CenterContainer/VBoxContainer"] 26 | layout_mode = 2 27 | text = "Click Me" 28 | script = ExtResource("1") 29 | 30 | [node name="Label2" type="Label" parent="CenterContainer/VBoxContainer"] 31 | visible = false 32 | layout_mode = 2 33 | text = "Your VMF is at %appdata%/Godot/NameOfProjectHere/output.vmf" 34 | 35 | [node name="Button2" type="Button" parent="CenterContainer/VBoxContainer"] 36 | visible = false 37 | layout_mode = 2 38 | text = "Show VMF" 39 | 40 | [connection signal="pressed" from="CenterContainer/VBoxContainer/Button" to="CenterContainer/VBoxContainer/Button" method="_on_pressed"] 41 | [connection signal="pressed" from="CenterContainer/VBoxContainer/Button2" to="CenterContainer/VBoxContainer/Button" method="_on_Button2_pressed"] 42 | -------------------------------------------------------------------------------- /addons/vmflib/examples/Example2.gd: -------------------------------------------------------------------------------- 1 | extends Button 2 | 3 | 4 | func _on_pressed() -> void: 5 | var vmf = VMF.new(VMF.GAMES.PORTAL_2) 6 | 7 | var block := VMF.Block.new(vmf, Vector3(0, 0, 0), Vector3(256, 256, 256), "DEV/DEV_MEASUREGENERIC01") 8 | block.brush.children[0].material = "DEV/DEV_MEASUREGENERIC01B" 9 | vmf.add_solid(block) 10 | 11 | var info_player_start := VMF.InfoPlayerStartEntity.new(vmf) 12 | 13 | vmf.write_to_file("user://output.vmf") 14 | 15 | get_parent().get_node("Label2").show() 16 | get_parent().get_node("Button2").show() 17 | 18 | 19 | func _on_Button2_pressed() -> void: 20 | OS.shell_open(OS.get_user_data_dir()) 21 | -------------------------------------------------------------------------------- /addons/vmflib/examples/Example2.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=3 uid="uid://luv5eaqrs8r2"] 2 | 3 | [ext_resource type="Script" path="res://addons/vmflib/examples/Example2.gd" id="1"] 4 | 5 | [node name="Example2" type="Control"] 6 | layout_mode = 3 7 | anchors_preset = 15 8 | anchor_right = 1.0 9 | anchor_bottom = 1.0 10 | grow_horizontal = 2 11 | grow_vertical = 2 12 | 13 | [node name="CenterContainer" type="CenterContainer" parent="."] 14 | layout_mode = 0 15 | anchor_right = 1.0 16 | anchor_bottom = 1.0 17 | 18 | [node name="VBoxContainer" type="VBoxContainer" parent="CenterContainer"] 19 | layout_mode = 2 20 | 21 | [node name="Label" type="Label" parent="CenterContainer/VBoxContainer"] 22 | layout_mode = 2 23 | text = "Generate a single cube:" 24 | 25 | [node name="Button" type="Button" parent="CenterContainer/VBoxContainer"] 26 | layout_mode = 2 27 | text = "Click Me" 28 | script = ExtResource("1") 29 | 30 | [node name="Label2" type="Label" parent="CenterContainer/VBoxContainer"] 31 | visible = false 32 | layout_mode = 2 33 | text = "Your VMF is at %appdata%/Godot/NameOfProjectHere/output.vmf" 34 | 35 | [node name="Button2" type="Button" parent="CenterContainer/VBoxContainer"] 36 | visible = false 37 | layout_mode = 2 38 | text = "Show VMF" 39 | 40 | [connection signal="pressed" from="CenterContainer/VBoxContainer/Button" to="CenterContainer/VBoxContainer/Button" method="_on_pressed"] 41 | [connection signal="pressed" from="CenterContainer/VBoxContainer/Button2" to="CenterContainer/VBoxContainer/Button" method="_on_Button2_pressed"] 42 | -------------------------------------------------------------------------------- /addons/vmflib/icon/.gdignore: -------------------------------------------------------------------------------- 1 | icon.png -------------------------------------------------------------------------------- /addons/vmflib/icon/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/craftablescience/vmflib-godot/7ff65e968449c34e1d74b7fa4955e6f8389187f1/addons/vmflib/icon/icon.png -------------------------------------------------------------------------------- /addons/vmflib/plugin.cfg: -------------------------------------------------------------------------------- 1 | [plugin] 2 | 3 | name="vmflib" 4 | description="A port of the vmflib2 code at https://github.com/Trainzack/vmflib2." 5 | author="craftablescience" 6 | version="0.2.2" 7 | script="vmflib_plugin.gd" 8 | -------------------------------------------------------------------------------- /addons/vmflib/vmflib_plugin.gd: -------------------------------------------------------------------------------- 1 | extends EditorPlugin 2 | 3 | 4 | func _enter_tree() -> void: 5 | pass 6 | 7 | func _exit_tree() -> void: 8 | pass 9 | -------------------------------------------------------------------------------- /icon.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /icon.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://d2is1ysandxon" 6 | path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://icon.svg" 14 | dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | svg/scale=1.0 36 | editor/scale_with_editor_scale=false 37 | editor/convert_colors_with_editor_theme=false 38 | -------------------------------------------------------------------------------- /project.godot: -------------------------------------------------------------------------------- 1 | ; Engine configuration file. 2 | ; It's best edited using the editor UI and not directly, 3 | ; since the parameters that go here are not all obvious. 4 | ; 5 | ; Format: 6 | ; [section] ; section goes between [] 7 | ; param=value ; assign values to parameters 8 | 9 | config_version=5 10 | 11 | [application] 12 | 13 | config/name="vmflib-godot" 14 | config/features=PackedStringArray("4.0", "Forward Plus") 15 | config/icon="res://icon.svg" 16 | 17 | [editor_plugins] 18 | 19 | enabled=PackedStringArray() 20 | --------------------------------------------------------------------------------