1038 lines
29 KiB
C++
1038 lines
29 KiB
C++
/* Copyright (C) 1996-1997 Id Software, Inc.
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 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 General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
|
|
See file, 'COPYING', for details.
|
|
*/
|
|
|
|
#include <cstdint>
|
|
|
|
#include <common/entdata.h>
|
|
#include <common/parser.hh>
|
|
#include <common/log.hh>
|
|
#include <common/mapfile.hh>
|
|
#include <common/settings.hh>
|
|
#include <common/imglib.hh>
|
|
#include <common/bsputils.hh>
|
|
#include <common/json.hh>
|
|
|
|
#ifdef USE_LUA
|
|
extern "C"
|
|
{
|
|
#include <lua.h>
|
|
#include <lualib.h>
|
|
#include <lauxlib.h>
|
|
}
|
|
#endif
|
|
|
|
using namespace mapfile;
|
|
|
|
// global map file state
|
|
map_file_t map_file;
|
|
const gamedef_t *current_game = nullptr;
|
|
|
|
struct maputil_settings : public settings::common_settings
|
|
{
|
|
private:
|
|
template<typename TSetting, typename T>
|
|
bool load_setting(const std::string &name, parser_base_t &parser, settings::source src, T default_value)
|
|
{
|
|
TSetting *setting = new TSetting(nullptr, name, "");
|
|
bool parsed = setting->parse(name, parser, src);
|
|
operations.push_back(std::unique_ptr<settings::setting_base>(setting));
|
|
return parsed;
|
|
}
|
|
|
|
bool load_setting(const std::string &name, settings::source src)
|
|
{
|
|
settings::setting_func *setting = new settings::setting_func(nullptr, name, nullptr);
|
|
operations.push_back(std::unique_ptr<settings::setting_base>(setting));
|
|
return true;
|
|
}
|
|
|
|
public:
|
|
settings::setting_func load{this, "load", [&](const std::string &name, parser_base_t &parser, settings::source src) {
|
|
return this->load_setting<settings::setting_string>(name, parser, src, "");
|
|
}, nullptr, "Load the given .map file"};
|
|
settings::setting_func game{this, "game", [&](const std::string &name, parser_base_t &parser, settings::source src) {
|
|
return this->load_setting<settings::setting_string>(name, parser, src, "");
|
|
}, nullptr, "Set game to process map as (must be done after load)"};
|
|
settings::setting_func script{this, "script", [&](const std::string &name, parser_base_t &parser, settings::source src) {
|
|
return this->load_setting<settings::setting_string>(name, parser, src, "");
|
|
}, nullptr, "Run the given script file"};
|
|
settings::setting_func save{this, "save", [&](const std::string &name, parser_base_t &parser, settings::source src) {
|
|
return this->load_setting<settings::setting_string>(name, parser, src, "");
|
|
}, nullptr, "Save the .map to the specified output"};
|
|
settings::setting_func query{this, "query", [&](const std::string &name, parser_base_t &parser, settings::source src) {
|
|
return this->load_setting<settings::setting_string>(name, parser, src, "");
|
|
}, nullptr, "perform a query on entities and print out matching results. see docs for more details on globals. note that query has the same access as script but is more suitable for small read-only operations."};
|
|
settings::setting_func convert{this, "convert", [&](const std::string &name, parser_base_t &parser, settings::source src) {
|
|
return this->load_setting<settings::setting_string>(name, parser, src, "");
|
|
}, nullptr, "convert the current map to the given format."};
|
|
settings::setting_func strip_extended_info{this, "strip_extended_info", [&](const std::string &name, parser_base_t &parser, settings::source src) {
|
|
return this->load_setting(name, src);
|
|
}, nullptr, "removes extended Quake II/III information on faces."};
|
|
|
|
std::vector<std::unique_ptr<settings::setting_base>> operations;
|
|
};
|
|
|
|
maputil_settings maputil_options;
|
|
|
|
map_file_t LoadMapOrEntFile(const fs::path &source)
|
|
{
|
|
logging::funcheader();
|
|
|
|
auto file = fs::load(source);
|
|
map_file_t map;
|
|
|
|
if (!file) {
|
|
FError("Couldn't load map/entity file \"{}\".", source);
|
|
return map;
|
|
}
|
|
|
|
parser_t parser(file, {source.string()});
|
|
|
|
map.parse(parser);
|
|
|
|
return map;
|
|
}
|
|
|
|
#ifdef USE_LUA
|
|
using array_iterate_callback = std::function<bool(lua_State *state, size_t index)>;
|
|
|
|
void lua_iterate_array(lua_State *state, array_iterate_callback cb)
|
|
{
|
|
size_t n = 0;
|
|
|
|
bool keep_iterating = false;
|
|
|
|
do {
|
|
lua_rawgeti(state, -1, n + 1);
|
|
|
|
if (lua_type(state, -1) != LUA_TNIL) {
|
|
keep_iterating = cb(state, n);
|
|
} else {
|
|
keep_iterating = false;
|
|
}
|
|
|
|
lua_pop(state, 1);
|
|
|
|
n++;
|
|
} while (keep_iterating);
|
|
}
|
|
|
|
size_t lua_count_array(lua_State *state)
|
|
{
|
|
size_t num = 0;
|
|
|
|
lua_iterate_array(state, [&num](auto state, auto index) {
|
|
num++;
|
|
return true;
|
|
});
|
|
|
|
return num;
|
|
}
|
|
|
|
// pushes value onto stack
|
|
static void json_to_lua(lua_State *state, const json &value)
|
|
{
|
|
switch (value.type()) {
|
|
case json::value_t::object: {
|
|
lua_newtable(state);
|
|
|
|
for (auto it = value.begin(); it != value.end(); ++it) {
|
|
lua_pushstring(state, it.key().c_str());
|
|
json_to_lua(state, it.value());
|
|
lua_settable(state, -3);
|
|
}
|
|
return;
|
|
}
|
|
case json::value_t::array: {
|
|
lua_newtable(state);
|
|
|
|
size_t i = 1;
|
|
|
|
for (auto &v : value) {
|
|
json_to_lua(state, v);
|
|
lua_rawseti(state, -2, i++);
|
|
}
|
|
return;
|
|
}
|
|
case json::value_t::string: {
|
|
lua_pushstring(state, value.get<std::string>().c_str());
|
|
return;
|
|
}
|
|
case json::value_t::number_unsigned: {
|
|
lua_pushnumber(state, value.get<uint64_t>());
|
|
return;
|
|
}
|
|
case json::value_t::number_integer: {
|
|
lua_pushnumber(state, value.get<int64_t>());
|
|
return;
|
|
}
|
|
case json::value_t::number_float: {
|
|
lua_pushnumber(state, value.get<double>());
|
|
return;
|
|
}
|
|
case json::value_t::boolean: {
|
|
lua_pushboolean(state, value.get<bool>());
|
|
return;
|
|
}
|
|
case json::value_t::null: {
|
|
lua_pushnil(state);
|
|
return;
|
|
}
|
|
default: {
|
|
luaL_error(state, "invalid JSON object type\n");
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
static int l_load_json(lua_State *state)
|
|
{
|
|
const char *path = lua_tostring(state, -1);
|
|
lua_pop(state, 1);
|
|
|
|
auto result = fs::load(path);
|
|
|
|
if (!result) {
|
|
return luaL_error(state, "can't load JSON file: %s\n", path);
|
|
}
|
|
|
|
try
|
|
{
|
|
auto json = json::parse(result->begin(), result->end());
|
|
|
|
json_to_lua(state, json);
|
|
}
|
|
catch(std::exception &e)
|
|
{
|
|
return luaL_error(state, "JSON load exception for %s: %s\n", path, e.what());
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Lua layout:
|
|
* entities = table[]
|
|
* [E].dict = array
|
|
* [D] = [ key, value ]
|
|
* [E].brushes = table[]
|
|
* [S].texture = string
|
|
* [S].plane_points = [ [ x, y, z ] [ x, y, z ] [ x, y, z ] ]
|
|
* [S].raw = table (can only contain ONE member:)
|
|
* .quaked = table
|
|
* .shift = [ x, y ]
|
|
* .rotate = number
|
|
* .scale = [ x, y ]
|
|
* .valve = table
|
|
* .axis = [ [ x, y, z ] [ x, y, z ] ]
|
|
* .shift = [ x, y ]
|
|
* .rotate = number
|
|
* .scale = [ x, y ]
|
|
* .bp = table
|
|
* .axis = [ [ x, y, z ] [ x, y, z ] ]
|
|
* .etp = table
|
|
* .shift = [ x, y ]
|
|
* .rotate = number
|
|
* .scale = [ x, y ]
|
|
* .tx2 = boolean
|
|
* [S].info = table or nil
|
|
* .contents = number
|
|
* .value = number
|
|
* .flags = number
|
|
* [S].plane = [ x, y, z, d ] (read-only)
|
|
* [S].vecs = [ [ x, y, z, d ] [ x, y, z, d ] ] (read-only)
|
|
*/
|
|
|
|
static void maputil_make_brush_side(lua_State *state, const brush_side_t &side)
|
|
{
|
|
// make side
|
|
lua_createtable(state, 0, 4);
|
|
|
|
// make vecs
|
|
lua_createtable(state, 2, 0);
|
|
|
|
for (size_t i = 0; i < 2; i++) {
|
|
lua_createtable(state, 4, 0);
|
|
|
|
for (size_t v = 0; v < 4; v++) {
|
|
lua_pushnumber(state, side.vecs.at(i, v));
|
|
lua_rawseti(state, -2, v + 1);
|
|
}
|
|
|
|
lua_rawseti(state, -2, i + 1);
|
|
}
|
|
|
|
lua_setfield(state, -2, "vecs");
|
|
|
|
// set raw
|
|
lua_createtable(state, 0, 1);
|
|
|
|
lua_createtable(state, 0, 4);
|
|
|
|
if (std::holds_alternative<texdef_quake_ed_t>(side.raw) ||
|
|
std::holds_alternative<texdef_valve_t>(side.raw) ||
|
|
std::holds_alternative<texdef_etp_t>(side.raw)) {
|
|
const texdef_quake_ed_t &raw = std::holds_alternative<texdef_quake_ed_t>(side.raw) ? std::get<texdef_quake_ed_t>(side.raw) :
|
|
std::holds_alternative<texdef_valve_t>(side.raw) ? reinterpret_cast<const texdef_quake_ed_t &>(std::get<texdef_valve_t>(side.raw)) :
|
|
reinterpret_cast<const texdef_quake_ed_t &>(std::get<texdef_etp_t>(side.raw));
|
|
|
|
lua_createtable(state, 2, 0);
|
|
lua_pushnumber(state, raw.shift[0]);
|
|
lua_rawseti(state, -2, 1);
|
|
lua_pushnumber(state, raw.shift[1]);
|
|
lua_rawseti(state, -2, 2);
|
|
lua_setfield(state, -2, "shift");
|
|
|
|
lua_pushnumber(state, raw.rotate);
|
|
lua_setfield(state, -2, "rotate");
|
|
|
|
lua_createtable(state, 2, 0);
|
|
lua_pushnumber(state, raw.scale[0]);
|
|
lua_rawseti(state, -2, 1);
|
|
lua_pushnumber(state, raw.scale[1]);
|
|
lua_rawseti(state, -2, 2);
|
|
lua_setfield(state, -2, "scale");
|
|
|
|
if (std::holds_alternative<texdef_etp_t>(side.raw)) {
|
|
const auto &raw_etp = std::get<texdef_etp_t>(side.raw);
|
|
|
|
lua_pushboolean(state, raw_etp.tx2);
|
|
lua_setfield(state, -2, "tx2");
|
|
}
|
|
}
|
|
|
|
if (std::holds_alternative<texdef_valve_t>(side.raw) ||
|
|
std::holds_alternative<texdef_bp_t>(side.raw)) {
|
|
const texdef_bp_t &raw_bp =
|
|
std::holds_alternative<texdef_valve_t>(side.raw) ? std::get<texdef_valve_t>(side.raw) :
|
|
std::get<texdef_bp_t>(side.raw);
|
|
|
|
lua_createtable(state, 2, 0);
|
|
|
|
for (size_t i = 0; i < 2; i++) {
|
|
lua_createtable(state, 3, 0);
|
|
for (size_t v = 0; v < 3; v++) {
|
|
lua_pushnumber(state, raw_bp.axis.at(i, v));
|
|
lua_rawseti(state, -2, v + 1);
|
|
}
|
|
lua_rawseti(state, -2, i + 1);
|
|
}
|
|
|
|
lua_setfield(state, -2, "axis");
|
|
}
|
|
|
|
if (std::holds_alternative<texdef_quake_ed_t>(side.raw)) {
|
|
lua_setfield(state, -2, "quaked");
|
|
} if (std::holds_alternative<texdef_etp_t>(side.raw)) {
|
|
lua_setfield(state, -2, "etp");
|
|
} if (std::holds_alternative<texdef_valve_t>(side.raw)) {
|
|
lua_setfield(state, -2, "valve");
|
|
} else {
|
|
lua_setfield(state, -2, "bp");
|
|
}
|
|
|
|
lua_setfield(state, -2, "raw");
|
|
|
|
// make plane
|
|
lua_createtable(state, 4, 0);
|
|
lua_pushnumber(state, side.plane.normal[0]);
|
|
lua_rawseti(state, -2, 1);
|
|
lua_pushnumber(state, side.plane.normal[1]);
|
|
lua_rawseti(state, -2, 2);
|
|
lua_pushnumber(state, side.plane.normal[2]);
|
|
lua_rawseti(state, -2, 3);
|
|
lua_pushnumber(state, side.plane.dist);
|
|
lua_rawseti(state, -2, 4);
|
|
lua_setfield(state, -2, "plane");
|
|
|
|
// make plane points
|
|
lua_createtable(state, 3, 0);
|
|
|
|
for (size_t i = 0; i < 3; i++) {
|
|
lua_createtable(state, 3, 0);
|
|
|
|
for (size_t v = 0; v < 3; v++) {
|
|
lua_pushnumber(state, side.planepts[i][v]);
|
|
lua_rawseti(state, -2, v + 1);
|
|
}
|
|
|
|
lua_rawseti(state, -2, i + 1);
|
|
}
|
|
|
|
lua_setfield(state, -2, "plane_points");
|
|
|
|
// set texture
|
|
lua_pushstring(state, side.texture.c_str());
|
|
lua_setfield(state, -2, "texture");
|
|
|
|
if (side.extended_info) {
|
|
// set info
|
|
lua_createtable(state, 0, 3);
|
|
lua_pushnumber(state, side.extended_info->contents);
|
|
lua_setfield(state, -2, "contents");
|
|
lua_pushnumber(state, side.extended_info->value);
|
|
lua_setfield(state, -2, "value");
|
|
lua_pushnumber(state, side.extended_info->flags.native);
|
|
lua_setfield(state, -2, "flags");
|
|
|
|
lua_setfield(state, -2, "info");
|
|
}
|
|
}
|
|
|
|
static void maputil_make_brush(lua_State *state, const brush_t &brush)
|
|
{
|
|
// make sides
|
|
lua_createtable(state, brush.faces.size(), 0);
|
|
|
|
for (size_t s = 0; s < brush.faces.size(); s++) {
|
|
auto &side = brush.faces[s];
|
|
|
|
maputil_make_brush_side(state, side);
|
|
|
|
// put side into sides
|
|
lua_rawseti(state, -2, s + 1);
|
|
}
|
|
}
|
|
|
|
#define LUA_VERIFY_TOP_TYPE(type) \
|
|
Q_assert(lua_type(state, -1) == type)
|
|
|
|
static void maputil_copy_dict(lua_State *state, map_entity_t &entity)
|
|
{
|
|
LUA_VERIFY_TOP_TYPE(LUA_TTABLE);
|
|
|
|
// check for dict
|
|
if (lua_getfield(state, -1, "dict") == LUA_TTABLE) {
|
|
|
|
// iterate kvps
|
|
size_t n = 0;
|
|
|
|
while (true) {
|
|
lua_rawgeti(state, -1, n + 1);
|
|
|
|
if (lua_type(state, -1) == LUA_TNIL) {
|
|
lua_pop(state, 1);
|
|
break;
|
|
}
|
|
|
|
LUA_VERIFY_TOP_TYPE(LUA_TTABLE);
|
|
n++;
|
|
|
|
lua_rawgeti(state, -1, 1);
|
|
const char *key = lua_tostring(state, -1);
|
|
lua_pop(state, 1);
|
|
lua_rawgeti(state, -1, 2);
|
|
const char *value = lua_tostring(state, -1);
|
|
lua_pop(state, 1);
|
|
entity.epairs.set(key, value);
|
|
|
|
lua_pop(state, 1);
|
|
}
|
|
}
|
|
|
|
lua_pop(state, 1);
|
|
}
|
|
|
|
static texdef_quake_ed_t maputil_load_quaked(lua_State *state)
|
|
{
|
|
texdef_quake_ed_t quaked;
|
|
|
|
lua_getfield(state, -1, "shift");
|
|
for (size_t i = 0; i < 2; i++) {
|
|
lua_rawgeti(state, -1, i + 1);
|
|
quaked.shift[i] = lua_tonumber(state, -1);
|
|
lua_pop(state, 1);
|
|
}
|
|
lua_pop(state, 1);
|
|
|
|
lua_getfield(state, -1, "rotate");
|
|
quaked.rotate = lua_tonumber(state, -1);
|
|
lua_pop(state, 1);
|
|
|
|
lua_getfield(state, -1, "scale");
|
|
for (size_t i = 0; i < 2; i++) {
|
|
lua_rawgeti(state, -1, i + 1);
|
|
quaked.scale[i] = lua_tonumber(state, -1);
|
|
lua_pop(state, 1);
|
|
}
|
|
lua_pop(state, 1);
|
|
|
|
return quaked;
|
|
}
|
|
|
|
static texdef_bp_t maputil_load_bp(lua_State *state)
|
|
{
|
|
texdef_bp_t bp;
|
|
|
|
lua_getfield(state, -1, "axis");
|
|
|
|
for (size_t i = 0; i < 2; i++) {
|
|
lua_rawgeti(state, -1, i + 1);
|
|
|
|
for (size_t v = 0; v < 3; v++) {
|
|
lua_rawgeti(state, -1, v + 1);
|
|
bp.axis.at(i, v) = lua_tonumber(state, -1);
|
|
lua_pop(state, 1);
|
|
}
|
|
|
|
lua_pop(state, 1);
|
|
}
|
|
|
|
lua_pop(state, 1);
|
|
|
|
return bp;
|
|
}
|
|
|
|
static void maputil_copy_side(lua_State *state, brush_side_t &side)
|
|
{
|
|
// texture
|
|
lua_getfield(state, -1, "texture");
|
|
side.texture = lua_tostring(state, -1);
|
|
lua_pop(state, 1);
|
|
|
|
// plane points
|
|
lua_getfield(state, -1, "plane_points");
|
|
|
|
for (size_t i = 0; i < 3; i++) {
|
|
lua_rawgeti(state, -1, i + 1);
|
|
|
|
for (size_t z = 0; z < 3; z++) {
|
|
lua_rawgeti(state, -1, z + 1);
|
|
side.planepts[i][z] = lua_tonumber(state, -1);
|
|
lua_pop(state, 1);
|
|
}
|
|
|
|
lua_pop(state, 1);
|
|
}
|
|
|
|
lua_pop(state, 1);
|
|
|
|
// raw
|
|
lua_getfield(state, -1, "raw");
|
|
|
|
if (lua_getfield(state, -1, "quaked") != LUA_TNIL) {
|
|
side.raw = maputil_load_quaked(state);
|
|
}
|
|
lua_pop(state, 1);
|
|
|
|
if (lua_getfield(state, -1, "valve") != LUA_TNIL) {
|
|
texdef_bp_t bp = maputil_load_bp(state);
|
|
texdef_quake_ed_t qed = maputil_load_quaked(state);
|
|
|
|
side.raw = texdef_valve_t { qed, bp };
|
|
}
|
|
lua_pop(state, 1);
|
|
|
|
if (lua_getfield(state, -1, "bp") != LUA_TNIL) {
|
|
side.raw = maputil_load_bp(state);
|
|
}
|
|
lua_pop(state, 1);
|
|
|
|
if (lua_getfield(state, -1, "etp") != LUA_TNIL) {
|
|
texdef_quake_ed_t qed = maputil_load_quaked(state);
|
|
|
|
lua_getfield(state, -1, "tx2");
|
|
bool b = !!lua_toboolean(state, -1);
|
|
lua_pop(state, 1);
|
|
|
|
side.raw = texdef_etp_t { qed, b };
|
|
}
|
|
lua_pop(state, 1);
|
|
|
|
lua_pop(state, 1);
|
|
|
|
// extra info
|
|
lua_getfield(state, -1, "info");
|
|
|
|
if (lua_type(state, -1) == LUA_TTABLE) {
|
|
texinfo_quake2_t q2;
|
|
|
|
lua_getfield(state, -1, "contents");
|
|
q2.contents = lua_tonumber(state, -1);
|
|
lua_pop(state, 1);
|
|
|
|
lua_getfield(state, -1, "value");
|
|
q2.value = lua_tonumber(state, -1);
|
|
lua_pop(state, 1);
|
|
|
|
lua_getfield(state, -1, "flags");
|
|
q2.flags.native = lua_tonumber(state, -1);
|
|
lua_pop(state, 1);
|
|
|
|
side.extended_info = q2;
|
|
}
|
|
|
|
lua_pop(state, 1);
|
|
}
|
|
|
|
static void maputil_copy_brush(lua_State *state, brush_t &brush)
|
|
{
|
|
// count sides
|
|
size_t num_sides = lua_count_array(state);
|
|
brush.faces.resize(num_sides);
|
|
|
|
// iterate brushes
|
|
lua_iterate_array(state, [&brush](auto state, auto index) {
|
|
brush_side_t &side = brush.faces[index];
|
|
maputil_copy_side(state, side);
|
|
return true;
|
|
});
|
|
}
|
|
|
|
static void maputil_copy_brushes(lua_State *state, map_entity_t &entity)
|
|
{
|
|
LUA_VERIFY_TOP_TYPE(LUA_TTABLE);
|
|
|
|
// check for dict
|
|
if (lua_getfield(state, -1, "brushes") == LUA_TTABLE) {
|
|
|
|
// count brushes
|
|
size_t num_brushes = lua_count_array(state);
|
|
entity.brushes.resize(num_brushes);
|
|
|
|
// iterate brushes
|
|
lua_iterate_array(state, [&entity](auto state, auto index) {
|
|
brush_t &brush = entity.brushes[index];
|
|
maputil_copy_brush(state, brush);
|
|
return true;
|
|
});
|
|
}
|
|
|
|
lua_pop(state, 1);
|
|
}
|
|
|
|
static int l_commit_map(lua_State *state)
|
|
{
|
|
map_file.entities.clear();
|
|
|
|
// verify entities global
|
|
lua_getglobal(state, "entities");
|
|
|
|
LUA_VERIFY_TOP_TYPE(LUA_TTABLE);
|
|
|
|
// count entities
|
|
size_t num_entities = lua_count_array(state);
|
|
|
|
// create entities
|
|
map_file.entities.resize(num_entities);
|
|
|
|
for (size_t i = 0; i < num_entities; i++) {
|
|
auto &entity = map_file.entities[i];
|
|
|
|
lua_rawgeti(state, -1, i + 1);
|
|
|
|
maputil_copy_dict(state, entity);
|
|
|
|
maputil_copy_brushes(state, entity);
|
|
|
|
lua_pop(state, 1);
|
|
}
|
|
|
|
lua_pop(state, 1);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline qplane3d pop_plane_from_side(lua_State *state)
|
|
{
|
|
qplane3d plane;
|
|
|
|
lua_getfield(state, -1, "plane");
|
|
|
|
for (size_t i = 0; i < 3; i++) {
|
|
lua_rawgeti(state, -1, i + 1);
|
|
plane.normal[i] = lua_tonumber(state, -1);
|
|
lua_pop(state, 1);
|
|
}
|
|
lua_rawgeti(state, -1, 4);
|
|
plane.dist = lua_tonumber(state, -1);
|
|
lua_pop(state, 1);
|
|
|
|
lua_pop(state, 1);
|
|
|
|
return plane;
|
|
}
|
|
|
|
static int l_create_winding(lua_State *state)
|
|
{
|
|
// -3 = face
|
|
// -2 = brush
|
|
// -1 = extents
|
|
bool found_face = false;
|
|
|
|
double extents = lua_tonumber(state, -1);
|
|
lua_pop(state, 1);
|
|
|
|
lua_pushvalue(state, -2);
|
|
qplane3d side_plane = pop_plane_from_side(state);
|
|
lua_pop(state, 1);
|
|
|
|
using winding_t = polylib::winding_base_t<polylib::winding_storage_hybrid_t<double, 16>>;
|
|
std::optional<winding_t> winding = winding_t::from_plane(side_plane, extents);
|
|
|
|
// loop through sides on brush
|
|
{
|
|
if (lua_type(state, -1) == LUA_TTABLE) {
|
|
lua_iterate_array(state, [&found_face, &winding](auto state, auto index) {
|
|
|
|
LUA_VERIFY_TOP_TYPE(LUA_TTABLE);
|
|
|
|
// check that the face is part of the brush
|
|
if (lua_rawequal(state, -1, -3)) {
|
|
found_face = true;
|
|
} else if (winding) {
|
|
qplane3d plane = pop_plane_from_side(state);
|
|
winding = winding->clip_front(-plane, 0.0f);
|
|
}
|
|
|
|
return true;
|
|
});
|
|
}
|
|
|
|
lua_pop(state, 1);
|
|
}
|
|
|
|
if (!winding) {
|
|
lua_pushnil(state);
|
|
} else {
|
|
lua_createtable(state, winding->size(), 0);
|
|
|
|
for (size_t i = 0; i < winding->size(); i++) {
|
|
lua_createtable(state, 3, 0);
|
|
|
|
auto &p = winding->at(i);
|
|
for (size_t v = 0; v < 3; v++) {
|
|
lua_pushnumber(state, p[v]);
|
|
lua_rawseti(state, -2, v + 1);
|
|
}
|
|
|
|
lua_rawseti(state, -2, i + 1);
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int l_load_texture_meta(lua_State *state)
|
|
{
|
|
const char *path = lua_tostring(state, 1); /* get argument */
|
|
lua_pop(state, 1);
|
|
|
|
if (!current_game) {
|
|
luaL_error(state, "need a game loaded with -game for this function");
|
|
}
|
|
|
|
auto result = std::get<0>(img::load_texture_meta(path, current_game, maputil_options)).value_or(img::texture_meta {});
|
|
|
|
lua_createtable(state, 0, 5);
|
|
|
|
lua_pushnumber(state, result.contents_native);
|
|
lua_setfield(state, -2, "contents");
|
|
lua_pushnumber(state, result.flags.native);
|
|
lua_setfield(state, -2, "flags");
|
|
lua_pushnumber(state, result.value);
|
|
lua_setfield(state, -2, "value");
|
|
lua_pushnumber(state, result.width);
|
|
lua_setfield(state, -2, "width");
|
|
lua_pushnumber(state, result.height);
|
|
lua_setfield(state, -2, "height");
|
|
|
|
return 1;
|
|
}
|
|
|
|
static void maputil_setup_globals(lua_State *state)
|
|
{
|
|
lua_pushcfunction(state, l_load_json);
|
|
lua_setglobal(state, "load_json");
|
|
|
|
lua_pushcfunction(state, l_commit_map);
|
|
lua_setglobal(state, "commit_map");
|
|
|
|
lua_pushcfunction(state, l_create_winding);
|
|
lua_setglobal(state, "create_winding");
|
|
|
|
lua_pushcfunction(state, l_load_texture_meta);
|
|
lua_setglobal(state, "load_texture_meta");
|
|
|
|
// constants
|
|
lua_pushnumber(state, (int32_t) texcoord_style_t::quaked);
|
|
lua_setglobal(state, "TEXCOORD_QUAKED");
|
|
lua_pushnumber(state, (int32_t) texcoord_style_t::etp);
|
|
lua_setglobal(state, "TEXCOORD_ETP");
|
|
lua_pushnumber(state, (int32_t) texcoord_style_t::valve_220);
|
|
lua_setglobal(state, "TEXCOORD_VALVE");
|
|
lua_pushnumber(state, (int32_t) texcoord_style_t::brush_primitives);
|
|
lua_setglobal(state, "TEXCOORD_BP");
|
|
|
|
// convert map to a Lua representation.
|
|
lua_createtable(state, map_file.entities.size(), 0);
|
|
|
|
for (size_t i = 0; i < map_file.entities.size(); i++) {
|
|
auto &entity = map_file.entities[i];
|
|
|
|
// make entity table
|
|
lua_createtable(state, 0, 2);
|
|
|
|
// make dict
|
|
if (entity.epairs.size()) {
|
|
lua_createtable(state, entity.epairs.size(), 0);
|
|
|
|
size_t ent = 0;
|
|
|
|
for (auto &pair : entity.epairs) {
|
|
lua_createtable(state, 2, 0);
|
|
lua_pushstring(state, pair.first.c_str());
|
|
lua_rawseti(state, -2, 1);
|
|
lua_pushstring(state, pair.second.c_str());
|
|
lua_rawseti(state, -2, 2);
|
|
|
|
lua_rawseti(state, -2, ent + 1);
|
|
ent++;
|
|
}
|
|
|
|
// push dict to entity
|
|
lua_setfield(state, -2, "dict");
|
|
}
|
|
|
|
// make brushes
|
|
if (!entity.brushes.empty()) {
|
|
lua_createtable(state, entity.brushes.size(), 0);
|
|
|
|
for (size_t b = 0; b < entity.brushes.size(); b++) {
|
|
auto &brush = entity.brushes[b];
|
|
|
|
maputil_make_brush(state, brush);
|
|
|
|
// put brush into brushes
|
|
lua_rawseti(state, -2, b + 1);
|
|
}
|
|
|
|
// push dict to entity
|
|
lua_setfield(state, -2, "brushes");
|
|
}
|
|
|
|
// put entity into entities
|
|
lua_rawseti(state, -2, i + 1);
|
|
}
|
|
|
|
lua_setglobal(state, "entities");
|
|
}
|
|
|
|
static lua_State *maputil_setup_lua()
|
|
{
|
|
lua_State *state = luaL_newstate();
|
|
|
|
luaL_openlibs(state);
|
|
|
|
return state;
|
|
}
|
|
|
|
static void maputil_free_lua(lua_State *state)
|
|
{
|
|
lua_close(state);
|
|
}
|
|
|
|
static int maputil_lua_error(lua_State *state)
|
|
{
|
|
luaL_traceback(state, state, NULL, 1);
|
|
|
|
logging::print("can't execute script: {}\n{}\n", lua_tostring(state, -1), lua_tostring(state, -2));
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static void maputil_exec_script(const fs::path &file)
|
|
{
|
|
#ifdef USE_LUA
|
|
lua_State *state = maputil_setup_lua();
|
|
|
|
lua_pushcfunction(state, maputil_lua_error);
|
|
|
|
int err = luaL_loadfile(state, file.string().c_str());
|
|
|
|
if (err != LUA_OK) {
|
|
logging::print("can't load script: {}\n", lua_tostring(state, -1));
|
|
} else {
|
|
maputil_setup_globals(state);
|
|
|
|
err = lua_pcall(state, 0, 0, -2);
|
|
|
|
if (err != LUA_OK) {
|
|
|
|
lua_pop(state, 1);
|
|
}
|
|
}
|
|
|
|
maputil_free_lua(state);
|
|
#else
|
|
logging::print("maputil not compiled with Lua support\n");
|
|
#endif
|
|
}
|
|
|
|
static void maputil_exec_query(const char *query)
|
|
{
|
|
#ifdef USE_LUA
|
|
logging::print("query: {}\n", query);
|
|
|
|
lua_State *state = maputil_setup_lua();
|
|
|
|
int err = luaL_loadstring(state, query);
|
|
|
|
if (err != LUA_OK) {
|
|
logging::print("can't load query: {}\n", lua_tostring(state, -1));
|
|
lua_pop(state, 1);
|
|
} else {
|
|
maputil_setup_globals(state);
|
|
|
|
lua_pushvalue(state, 1);
|
|
|
|
int ref = luaL_ref(state, LUA_REGISTRYINDEX);
|
|
|
|
lua_pop(state, 1);
|
|
|
|
for (auto &entity : map_file.entities) {
|
|
lua_createtable(state, 0, entity.epairs.size());
|
|
|
|
for (auto &kvp : entity.epairs) {
|
|
|
|
lua_pushstring(state, kvp.second.c_str());
|
|
lua_setfield(state, -2, kvp.first.c_str());
|
|
}
|
|
|
|
lua_setglobal(state, "entity");
|
|
|
|
lua_rawgeti(state, LUA_REGISTRYINDEX, ref);
|
|
err = lua_pcall(state, 0, 1, 0);
|
|
|
|
if (err != LUA_OK) {
|
|
logging::print("can't execute query: {}\n", lua_tostring(state, -1));
|
|
lua_pop(state, 1);
|
|
} else {
|
|
int b = lua_toboolean(state, -1);
|
|
lua_pop(state, 1);
|
|
|
|
if (b) {
|
|
logging::print("MATCHED: {} @ {}\n", entity.epairs.get("classname"), entity.location);
|
|
}
|
|
}
|
|
|
|
lua_gc(state, LUA_GCCOLLECT);
|
|
}
|
|
|
|
luaL_unref(state, LUA_REGISTRYINDEX, ref);
|
|
}
|
|
|
|
maputil_free_lua(state);
|
|
#else
|
|
logging::print("maputil not compiled with Lua support\n");
|
|
#endif
|
|
}
|
|
|
|
int maputil_main(int argc, const char **argv)
|
|
{
|
|
logging::preinitialize();
|
|
|
|
maputil_options.preinitialize(argc, argv);
|
|
maputil_options.initialize(argc - 1, argv + 1);
|
|
maputil_options.postinitialize(argc, argv);
|
|
|
|
logging::init(std::nullopt, maputil_options);
|
|
|
|
if (maputil_options.operations.empty()) {
|
|
maputil_options.print_help();
|
|
return 1;
|
|
}
|
|
|
|
fs::path source;
|
|
|
|
for (auto &operation : maputil_options.operations) {
|
|
|
|
if (operation->primary_name() == "load") {
|
|
source = operation->string_value();
|
|
|
|
if (!fs::exists(source)) {
|
|
source = DefaultExtension(source, "map");
|
|
}
|
|
|
|
logging::print("---------------------\n");
|
|
logging::print("{}\n", source);
|
|
|
|
map_file = LoadMapOrEntFile(source);
|
|
} else if (operation->primary_name() == "game") {
|
|
const std::string &gamename = operation->string_value();
|
|
|
|
current_game = nullptr;
|
|
|
|
for (auto &game : gamedef_list()) {
|
|
if (!Q_strcasecmp(game->friendly_name, gamename)) {
|
|
current_game = game;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!current_game) {
|
|
FError("not sure what game {} is\n", gamename);
|
|
}
|
|
|
|
current_game->init_filesystem(source, maputil_options);
|
|
} else if (operation->primary_name() == "script") {
|
|
maputil_exec_script(operation->string_value().c_str());
|
|
} else if (operation->primary_name() == "query") {
|
|
maputil_exec_query(operation->string_value().c_str());
|
|
} else if (operation->primary_name() == "strip_extended_info") {
|
|
for (auto &entity : map_file.entities) {
|
|
for (auto &brush : entity.brushes) {
|
|
for (auto &face : brush.faces) {
|
|
face.extended_info = std::nullopt;
|
|
}
|
|
}
|
|
}
|
|
} else if (operation->primary_name() == "convert") {
|
|
|
|
std::string type = operation->string_value();
|
|
texcoord_style_t dest_style;
|
|
|
|
if (string_iequals(type, "quake")) {
|
|
dest_style = texcoord_style_t::quaked;
|
|
} else if (string_iequals(type, "valve")) {
|
|
dest_style = texcoord_style_t::valve_220;
|
|
} else if (string_iequals(type, "etp")) {
|
|
dest_style = texcoord_style_t::etp;
|
|
} else if (string_iequals(type, "bp")) {
|
|
dest_style = texcoord_style_t::brush_primitives;
|
|
} else {
|
|
FError("unknown map style {}", type);
|
|
}
|
|
|
|
map_file.convert_to(dest_style, current_game, maputil_options);
|
|
} else if (operation->primary_name() == "save") {
|
|
fs::path dest = DefaultExtension(operation->string_value(), "map");
|
|
logging::print("saving to {}...\n", dest);
|
|
|
|
std::ofstream stream(dest);
|
|
map_file.write(stream);
|
|
} else {
|
|
Error("option not implemented: {}", operation->primary_name());
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|