quake 3 | translate quake map | replace known classes in map | Search

This code automates the process of standardizing or customizing textures in Quake 3 map files by replacing texture references with predefined replacements from a list of common textures and a custom override dictionary.

Run example

npm run import -- "replace common textures in map"

replace common textures in map

var fs = require('fs')
var path = require('path')
var importer = require('../Core')

var common = [
  'areaportal',    'botclip',
  'caulk',         'clip',
  'clusterportal', 'cushion',
  'donotenter',    'full_clip',
  'hint',          'ladderclip',
  'ladderclip',    'lightgrid',
  'metalclip',     'missileclip',
  'nodraw',        'nodrawnonsolid',
  'nodrop',        'nolightmap',
  'origin',        'qer_mirror',
  'qer_portal',    'slick',
  'terrain',       'trigger',
  'weapclip',      'white'
];
var textures = {
    
}
function replaceTextures(file) {
    var used = []
    
    if(typeof file === 'string' && fs.existsSync(file)) {
        file = fs.readFileSync(file).toString('utf-8')
    }
    
    // get a list of common textures
    if(!common) {
        var files = fs.readdirSync('/Users/briancullinan/planet_quake_data/quake3-baseq3/common-spog.pk3dir/textures/common')
        common = files.map(f => f.substr(0, f.length - path.extname(f).length));
    }
    
    // get all brushes in map, leaf nodes with at least one vertex
    var brushes = importer.regexToArray(/\{[\s\S^}]*?\}/ig, file)
    
    // replace all brushes with textures from common or berserker overrides
    brushes.forEach(b => {
        var newBrush = b
        Object.keys(textures).forEach(k => {
            newBrush = newBrush.replace(new RegExp('\\) ([^\\/\\)\\(]*?\\/)*?' + k, 'ig'),
                                        (str, $1) => (') ' + textures[k]))
        })
        common.forEach(k => {
            newBrush = newBrush.replace(new RegExp('\\) ([^\\/\\)\\(]*?\\/)*?' + k, 'ig'),
                                (str, $1) => (') common/' + (k === 'clip' ? 'hint' : k)))
        })
        // fix water, don't mix textures because
        //   trenchbroom rearranges verteces so water isn't always applied
        if(newBrush.includes('wter')) {
            newBrush = newBrush
                .replace(/\)\s+([^\)\(]*?)\s+((\s*[0-9\.-]+){5,8})/igm,
                         (str, $1, $2) => (') e1u1/bluwter ' + $2))
        }
        var texts = importer.regexToArray(/\)\s+([^\)\(]*?)\s+((\s*[0-9\.-]+){5,8})/igm, b, 1)
        texts.forEach($1 => {
            if(used.indexOf(path.dirname($1)) === -1) used.push(path.dirname($1))
        })
        
        file = file.replace(b, newBrush)
    })
    
    used = used.map(u => 'textures/' + u.replace('^\/|\/$|^\s*|\s*


, ''))
    file = file.replace(/"classname" "worldspawn"/ig,`"classname" "worldspawn"
"_tb_textures" "textures/common;${used.join(';')}"`)

    return file
}

module.exports = replaceTextures

What the code could have been:

const fs = require('fs');
const path = require('path');
const importer = require('../Core');

// Define common textures
const commonTextures = [
  'areaportal',    'botclip',
  'caulk',         'clip',
  'clusterportal', 'cushion',
  'donotenter',    'full_clip',
  'hint',          'ladderclip',
  'ladderclip',    'lightgrid',
 'metalclip',    'missileclip',
  'nodraw',        'nodrawnonsolid',
  'nodrop',        'nolightmap',
  'origin',        'qer_mirror',
  'qer_portal',   'slick',
  'terrain',       'trigger',
  'weapclip',      'white'
];

// Define textures replacements
const texturesReplacements = {
  // Add textures replacements here
};

/**
 * Replaces textures in a given Quake 3 file.
 * 
 * @param {string} file - The Quake 3 file to modify.
 * @returns {string} The modified Quake 3 file.
 */
function replaceTextures(file) {
  const usedTextures = new Set();
  
  // Check if file is a string and exists
  if (typeof file ==='string' && fs.existsSync(file)) {
    file = fs.readFileSync(file, 'utf-8');
  }
  
  // Get common textures from file system (if not defined)
  if (!commonTextures.length) {
    const commonTexturesDir = '/Users/briancullinan/planet_quake_data/quake3-baseq3/common-spog.pk3dir/textures/common';
    const commonTexturesFiles = fs.readdirSync(commonTexturesDir);
    commonTextures = commonTexturesFiles.map((f) => f.substr(0, f.length - path.extname(f).length));
  }
  
  // Get all brushes in map (leaf nodes with at least one vertex)
  const brushes = importer.regexToArray(/\{[\s\S^}]*?\}/ig, file);
  
  // Replace all brushes with textures from common or berserker overrides
  brushes.forEach((b) => {
    let newBrush = b;
    Object.keys(texturesReplacements).forEach((k) => {
      newBrush = newBrush.replace(new RegExp(`\\) ([^\\/\\)\\(]*?\\/)*?${k}`, 'ig'), (str, $1) => `) ${texturesReplacements[k]}`);
    });
    commonTextures.forEach((k) => {
      newBrush = newBrush.replace(new RegExp(`\\) ([^\\/\\)\\(]*?\\/)*?${k}`, 'ig'), (str, $1) => `) common/${k === 'clip'? 'hint' : k}`);
    });
    
    // Fix water, don't mix textures because trenchbroom rearranges vertices so water isn't always applied
    if (newBrush.includes('wter')) {
      newBrush = newBrush.replace(/\)\s+([^\)\(]*?)\s+((\s*[0-9\.-]+){5,8})/igm, (str, $1, $2) => `) e1u1/bluwter ${$2}`);
    }
    
    const texts = importer.regexToArray(/\)\s+([^\)\(]*?)\s+((\s*[0-9\.-]+){5,8})/igm, b, 1);
    texts.forEach((text) => {
      const textureDir = path.dirname(text);
      if (!usedTextures.has(textureDir)) {
        usedTextures.add(textureDir);
      }
    });
    
    // Replace brush with new brush
    file = file.replace(b, newBrush);
  });
  
  // Update _tb_textures line
  usedTextures = Array.from(usedTextures).map((u) => `textures/${u.replace(/^\/|\/$|^\s*|\s*$/, '')}`);
  file = file.replace(/"classname" "worldspawn"/ig, `"classname" "worldspawn"
"_tb_textures" "textures/common;${usedTextures.join(';")}"`);
  
  return file;
}

module.exports = replaceTextures;

This code snippet modifies a Quake 3 map file by replacing texture references with predefined replacements.

Here's a breakdown:

  1. Initialization:

  2. replaceTextures Function:

  3. Texture Replacement:

  4. Output:

Purpose:

This code likely automates the process of standardizing or customizing texture references in Quake 3 map files. It allows for replacing common textures with specific alternatives or applying custom overrides defined in the textures object.