189 lines
7.0 KiB
C++
189 lines
7.0 KiB
C++
#include "hmap/hmap.h"
|
|
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <sstream>
|
|
|
|
namespace hmap {
|
|
|
|
namespace {
|
|
|
|
const char* kBuiltin = R"DEF(
|
|
# Hammer++ built-in entity definitions (same syntax as assets/entities.def)
|
|
# @point|@brush <class> "<label>" [color(r,g,b)] [size(h)|size(hx,hy,hz)] [dir] [model] [contents(solid|clip|trigger|water|sky)]
|
|
# <key> <type> "<label>" "<default>" ["a=Label A,b=Label B"] (types: string int float bool color255 vec3 choice model material target)
|
|
|
|
@point info_player_start "Player start" color(70,230,90) size(16,36,16) dir
|
|
@point info_target "Target / marker" color(190,190,190) size(6)
|
|
|
|
@point light "Point light" color(255,225,110) size(6)
|
|
color color255 "Color" "255 240 215"
|
|
intensity float "Intensity" "1.0"
|
|
radius float "Radius" "512"
|
|
|
|
@point light_spot "Spot light" color(255,190,90) size(6) dir
|
|
color color255 "Color" "255 240 215"
|
|
intensity float "Intensity" "1.2"
|
|
radius float "Radius" "768"
|
|
cone_inner float "Inner cone (deg)" "25"
|
|
cone_outer float "Outer cone (deg)" "40"
|
|
|
|
@point light_environment "Sun / environment" color(255,205,70) size(10) dir
|
|
color color255 "Sun color" "255 244 222"
|
|
intensity float "Intensity" "0.9"
|
|
ambient color255 "Ambient" "42 46 58"
|
|
|
|
@point prop_static "Static prop" color(120,170,255) size(16) model
|
|
model model "Model" ""
|
|
solid bool "Collide" "1"
|
|
tint color255 "Tint" "255 255 255"
|
|
|
|
@point prop_dynamic "Dynamic prop" color(110,210,255) size(16) model
|
|
model model "Model" ""
|
|
solid bool "Collide" "1"
|
|
animation string "Animation" ""
|
|
|
|
@point sound_ambient "Ambient sound" color(200,130,255) size(8)
|
|
sound string "Sound file" ""
|
|
volume float "Volume" "1.0"
|
|
radius float "Radius" "512"
|
|
|
|
@point item_spawn "Item spawn" color(255,150,200) size(10)
|
|
item string "Item" ""
|
|
respawn float "Respawn time" "30"
|
|
|
|
@brush func_detail "Detail brush" color(120,160,255)
|
|
@brush func_wall "Wall (own model)" color(170,170,190)
|
|
@brush func_door "Door" color(210,165,90)
|
|
speed float "Speed" "100"
|
|
movedir vec3 "Move direction" "0 1 0"
|
|
distance float "Distance" "64"
|
|
wait float "Wait before closing" "3"
|
|
@brush func_button "Button" color(230,120,120)
|
|
target target "Target" ""
|
|
delay float "Delay" "0"
|
|
@brush trigger_multiple "Trigger (repeatable)" color(255,120,40) contents(trigger)
|
|
target target "Target" ""
|
|
wait float "Re-arm delay" "1"
|
|
@brush trigger_once "Trigger (once)" color(255,170,60) contents(trigger)
|
|
target target "Target" ""
|
|
@brush func_clip "Player clip" color(120,120,255) contents(clip)
|
|
@brush water "Water volume" color(70,120,255) contents(water)
|
|
density float "Density" "1.0"
|
|
)DEF";
|
|
|
|
std::vector<std::string> tokenize(const std::string& line) {
|
|
std::vector<std::string> t;
|
|
size_t i = 0, n = line.size();
|
|
while (i < n) {
|
|
while (i < n && (line[i] == ' ' || line[i] == '\t' || line[i] == '\r')) ++i;
|
|
if (i >= n) break;
|
|
if (line[i] == '"') {
|
|
size_t j = line.find('"', i + 1);
|
|
if (j == std::string::npos) j = n;
|
|
t.push_back(line.substr(i + 1, j - i - 1));
|
|
i = j + 1;
|
|
} else {
|
|
size_t j = i;
|
|
while (j < n && line[j] != ' ' && line[j] != '\t' && line[j] != '\r') ++j;
|
|
t.push_back(line.substr(i, j - i));
|
|
i = j;
|
|
}
|
|
}
|
|
return t;
|
|
}
|
|
|
|
std::vector<float> numbersIn(const std::string& tok) {
|
|
std::vector<float> v;
|
|
size_t a = tok.find('('), b = tok.find(')');
|
|
if (a == std::string::npos || b == std::string::npos || b < a) return v;
|
|
std::string inner = tok.substr(a + 1, b - a - 1);
|
|
for (char& c : inner) if (c == ',') c = ' ';
|
|
std::istringstream ss(inner);
|
|
float f;
|
|
while (ss >> f) v.push_back(f);
|
|
return v;
|
|
}
|
|
|
|
bool startsWith(const std::string& s, const char* p) { return s.rfind(p, 0) == 0; }
|
|
|
|
} // namespace
|
|
|
|
const EntityClass* EntityRegistry::find(const std::string& name) const {
|
|
for (const EntityClass& c : classes)
|
|
if (c.name == name) return &c;
|
|
return nullptr;
|
|
}
|
|
|
|
Status EntityRegistry::parse(const std::string& text) {
|
|
std::istringstream in(text);
|
|
std::string line;
|
|
int lineNo = 0;
|
|
EntityClass* cur = nullptr;
|
|
while (std::getline(in, line)) {
|
|
++lineNo;
|
|
size_t first = line.find_first_not_of(" \t\r");
|
|
if (first == std::string::npos || line[first] == '#') continue;
|
|
std::vector<std::string> t = tokenize(line);
|
|
if (t.empty()) continue;
|
|
if (t[0][0] == '@') {
|
|
if (t.size() < 2 || (t[0] != "@point" && t[0] != "@brush"))
|
|
return Status::Fail("entity defs line " + std::to_string(lineNo) + ": expected @point or @brush <class>");
|
|
EntityClass c;
|
|
c.name = t[1];
|
|
c.label = t.size() > 2 ? t[2] : t[1];
|
|
c.brush = t[0] == "@brush";
|
|
if (c.brush) c.brushContents = CONTENTS_SOLID;
|
|
for (size_t i = 3; i < t.size(); ++i) {
|
|
if (startsWith(t[i], "color(")) {
|
|
auto v = numbersIn(t[i]);
|
|
if (v.size() >= 3) c.color = {v[0] / 255.0f, v[1] / 255.0f, v[2] / 255.0f};
|
|
} else if (startsWith(t[i], "size(")) {
|
|
auto v = numbersIn(t[i]);
|
|
if (v.size() == 1) c.half = {v[0], v[0], v[0]};
|
|
else if (v.size() >= 3) c.half = {v[0], v[1], v[2]};
|
|
} else if (t[i] == "dir") c.directional = true;
|
|
else if (t[i] == "model") c.hasModel = true;
|
|
else if (startsWith(t[i], "contents(")) {
|
|
std::string v = t[i].substr(9);
|
|
if (v.find("trigger") == 0) c.brushContents = CONTENTS_TRIGGER;
|
|
else if (v.find("clip") == 0) c.brushContents = CONTENTS_CLIP;
|
|
else if (v.find("water") == 0) c.brushContents = CONTENTS_WATER;
|
|
else if (v.find("sky") == 0) c.brushContents = CONTENTS_SKY;
|
|
else c.brushContents = CONTENTS_SOLID;
|
|
}
|
|
}
|
|
bool replaced = false;
|
|
for (EntityClass& ex : classes)
|
|
if (ex.name == c.name) { ex = c; cur = &ex; replaced = true; break; }
|
|
if (!replaced) { classes.push_back(c); cur = &classes.back(); }
|
|
} else {
|
|
if (!cur) return Status::Fail("entity defs line " + std::to_string(lineNo) + ": property before any class");
|
|
if (t.size() < 3) return Status::Fail("entity defs line " + std::to_string(lineNo) + ": expected <key> <type> \"<label>\"");
|
|
PropDef p;
|
|
p.key = t[0];
|
|
p.type = t[1];
|
|
p.label = t[2];
|
|
if (t.size() > 3) p.def = t[3];
|
|
if (t.size() > 4) p.choices = t[4];
|
|
cur->props.push_back(p);
|
|
}
|
|
}
|
|
return Status::Ok();
|
|
}
|
|
|
|
void EntityRegistry::applyDefaults(Entity& e) const {
|
|
const EntityClass* c = find(e.classname);
|
|
if (!c) return;
|
|
for (const PropDef& p : c->props)
|
|
if (!p.def.empty() && !e.find(p.key)) e.set(p.key, p.def);
|
|
}
|
|
|
|
EntityRegistry EntityRegistry::builtin() {
|
|
EntityRegistry r;
|
|
r.parse(kBuiltin);
|
|
return r;
|
|
}
|
|
|
|
} // namespace hmap
|