// world.cpp: core map management stuff #include "blend.hh" #include "octa.hh" #include "pvs.hh" #include "rendergl.hh" /* fovy */ #include "renderlights.hh" #include "renderparticles.hh" #include "stain.hh" #include "texture.hh" #include "worldio.hh" #include "world.hh" #include "engine.hh" VARNR(mapscale, worldscale, 1, 0, 0); VARNR(mapsize, worldsize, 1, 0, 0); SVARR(maptitle, "Untitled Map by Unknown"); VARNR(emptymap, _emptymap, 1, 0, 0); VAR(octaentsize, 0, 64, 1024); VAR(entselradius, 0, 2, 10); static inline void transformbb(const entity &e, vec ¢er, vec &radius) { if(e.attr5 > 0) { float scale = e.attr5/100.0f; center.mul(scale); radius.mul(scale); } rotatebb(center, radius, e.attr2, e.attr3, e.attr4); } static inline void mmboundbox(const entity &e, model *m, vec ¢er, vec &radius) { m->boundbox(center, radius); transformbb(e, center, radius); } static inline void mmcollisionbox(const entity &e, model *m, vec ¢er, vec &radius) { m->collisionbox(center, radius); transformbb(e, center, radius); } static inline void decalboundbox(const entity &e, DecalSlot &s, vec ¢er, vec &radius) { float size = max(float(e.attr5), 1.0f); center = vec(0, s.depth * size/2, 0); radius = vec(size/2, s.depth * size/2, size/2); rotatebb(center, radius, e.attr2, e.attr3, e.attr4); } static bool getentboundingbox(const extentity &e, ivec &o, ivec &r) { switch(e.type) { case ET_EMPTY: return false; case ET_DECAL: { DecalSlot &s = lookupdecalslot(e.attr1, false); vec center, radius; decalboundbox(e, s, center, radius); center.add(e.o); radius.max(entselradius); o = ivec(vec(center).sub(radius)); r = ivec(vec(center).add(radius).add(1)); break; } case ET_MAPMODEL: if(model *m = loadmapmodel(e.attr1)) { vec center, radius; mmboundbox(e, m, center, radius); center.add(e.o); radius.max(entselradius); o = ivec(vec(center).sub(radius)); r = ivec(vec(center).add(radius).add(1)); break; } // invisible mapmodels use entselradius default: o = ivec(vec(e.o).sub(entselradius)); r = ivec(vec(e.o).add(entselradius+1)); break; } return true; } enum { MODOE_ADD = 1<<0, MODOE_UPDATEBB = 1<<1, MODOE_CHANGED = 1<<2 }; static void modifyoctaentity(int flags, int id, extentity &e, cube *c, const ivec &cor, int size, const ivec &bo, const ivec &br, int leafsize, vtxarray *lastva = NULL) { loopoctabox(cor, size, bo, br) { ivec o(i, cor, size); vtxarray *va = c[i].ext && c[i].ext->va ? c[i].ext->va : lastva; if(c[i].children != NULL && size > leafsize) modifyoctaentity(flags, id, e, c[i].children, o, size>>1, bo, br, leafsize, va); else if(flags&MODOE_ADD) { if(!c[i].ext || !c[i].ext->ents) ext(c[i]).ents = new octaentities(o, size); octaentities &oe = *c[i].ext->ents; switch(e.type) { case ET_DECAL: if(va) { va->bbmin.x = -1; if(oe.decals.empty()) va->decals.add(&oe); } oe.decals.add(id); oe.bbmin.min(bo).max(oe.o); oe.bbmax.max(br).min(ivec(oe.o).add(oe.size)); break; case ET_MAPMODEL: if(loadmapmodel(e.attr1)) { if(va) { va->bbmin.x = -1; if(oe.mapmodels.empty()) va->mapmodels.add(&oe); } oe.mapmodels.add(id); oe.bbmin.min(bo).max(oe.o); oe.bbmax.max(br).min(ivec(oe.o).add(oe.size)); break; } // invisible mapmodel default: oe.other.add(id); break; } } else if(c[i].ext && c[i].ext->ents) { octaentities &oe = *c[i].ext->ents; switch(e.type) { case ET_DECAL: oe.decals.removeobj(id); if(va) { va->bbmin.x = -1; if(oe.decals.empty()) va->decals.removeobj(&oe); } oe.bbmin = oe.bbmax = oe.o; oe.bbmin.add(oe.size); loopvj(oe.decals) { extentity &e = *entities::getents()[oe.decals[j]]; ivec eo, er; if(getentboundingbox(e, eo, er)) { oe.bbmin.min(eo); oe.bbmax.max(er); } } oe.bbmin.max(oe.o); oe.bbmax.min(ivec(oe.o).add(oe.size)); break; case ET_MAPMODEL: if(loadmapmodel(e.attr1)) { oe.mapmodels.removeobj(id); if(va) { va->bbmin.x = -1; if(oe.mapmodels.empty()) va->mapmodels.removeobj(&oe); } oe.bbmin = oe.bbmax = oe.o; oe.bbmin.add(oe.size); loopvj(oe.mapmodels) { extentity &e = *entities::getents()[oe.mapmodels[j]]; ivec eo, er; if(getentboundingbox(e, eo, er)) { oe.bbmin.min(eo); oe.bbmax.max(er); } } oe.bbmin.max(oe.o); oe.bbmax.min(ivec(oe.o).add(oe.size)); break; } // invisible mapmodel default: oe.other.removeobj(id); break; } if(oe.mapmodels.empty() && oe.decals.empty() && oe.other.empty()) freeoctaentities(c[i]); } if(c[i].ext && c[i].ext->ents) c[i].ext->ents->query = NULL; if(va && va!=lastva) { if(lastva) { if(va->bbmin.x < 0) lastva->bbmin.x = -1; } else if(flags&MODOE_UPDATEBB) updatevabb(va); } } } vector outsideents; int spotlights = 0, volumetriclights = 0, nospeclights = 0; static bool modifyoctaent(int flags, int id, extentity &e) { if(flags&MODOE_ADD ? e.flags&EF_OCTA : !(e.flags&EF_OCTA)) return false; ivec o, r; if(!getentboundingbox(e, o, r)) return false; if(!insideworld(e.o)) { int idx = outsideents.find(id); if(flags&MODOE_ADD) { if(idx < 0) outsideents.add(id); } else if(idx >= 0) outsideents.removeunordered(idx); } else { int leafsize = octaentsize, limit = max(r.x - o.x, max(r.y - o.y, r.z - o.z)); while(leafsize < limit) leafsize *= 2; int diff = ~(leafsize-1) & ((o.x^r.x)|(o.y^r.y)|(o.z^r.z)); if(diff && (limit > octaentsize/2 || diff < leafsize*2)) leafsize *= 2; modifyoctaentity(flags, id, e, worldroot, ivec(0, 0, 0), worldsize>>1, o, r, leafsize); } e.flags ^= EF_OCTA; switch(e.type) { case ET_LIGHT: clearlightcache(id); if(e.attr5&L_VOLUMETRIC) { if(flags&MODOE_ADD) volumetriclights++; else --volumetriclights; } if(e.attr5&L_NOSPEC) { if(!(flags&MODOE_ADD ? nospeclights++ : --nospeclights)) cleardeferredlightshaders(); } break; case ET_SPOTLIGHT: if(!(flags&MODOE_ADD ? spotlights++ : --spotlights)) { cleardeferredlightshaders(); cleanupvolumetric(); } break; case ET_PARTICLES: clearparticleemitters(); break; case ET_DECAL: if(flags&MODOE_CHANGED) changed(o, r, false); break; } return true; } static inline bool modifyoctaent(int flags, int id) { vector &ents = entities::getents(); return ents.inrange(id) && modifyoctaent(flags, id, *ents[id]); } static inline void addentity(int id) { modifyoctaent(MODOE_ADD|MODOE_UPDATEBB, id); } static inline void addentityedit(int id) { modifyoctaent(MODOE_ADD|MODOE_UPDATEBB|MODOE_CHANGED, id); } static inline void removeentity(int id) { modifyoctaent(MODOE_UPDATEBB, id); } static inline void removeentityedit(int id) { modifyoctaent(MODOE_UPDATEBB|MODOE_CHANGED, id); } void freeoctaentities(cube &c) { if(!c.ext) return; if(entities::getents().length()) { while(c.ext->ents && !c.ext->ents->mapmodels.empty()) removeentity(c.ext->ents->mapmodels.pop()); while(c.ext->ents && !c.ext->ents->decals.empty()) removeentity(c.ext->ents->decals.pop()); while(c.ext->ents && !c.ext->ents->other.empty()) removeentity(c.ext->ents->other.pop()); } if(c.ext->ents) { delete c.ext->ents; c.ext->ents = NULL; } } void entitiesinoctanodes() { vector &ents = entities::getents(); loopv(ents) modifyoctaent(MODOE_ADD, i, *ents[i]); } static inline void findents(octaentities &oe, int low, int high, bool notspawned, const vec &pos, const vec &invradius, vector &found) { vector &ents = entities::getents(); loopv(oe.other) { int id = oe.other[i]; extentity &e = *ents[id]; if(e.type >= low && e.type <= high && (e.spawned() || notspawned) && vec(e.o).sub(pos).mul(invradius).squaredlen() <= 1) found.add(id); } } static inline void findents(cube *c, const ivec &o, int size, const ivec &bo, const ivec &br, int low, int high, bool notspawned, const vec &pos, const vec &invradius, vector &found) { loopoctabox(o, size, bo, br) { if(c[i].ext && c[i].ext->ents) findents(*c[i].ext->ents, low, high, notspawned, pos, invradius, found); if(c[i].children && size > octaentsize) { ivec co(i, o, size); findents(c[i].children, co, size>>1, bo, br, low, high, notspawned, pos, invradius, found); } } } #if 0 void findents(int low, int high, bool notspawned, const vec &pos, const vec &radius, vector &found) { vec invradius(1/radius.x, 1/radius.y, 1/radius.z); ivec bo(vec(pos).sub(radius).sub(1)), br(vec(pos).add(radius).add(1)); int diff = (bo.x^br.x) | (bo.y^br.y) | (bo.z^br.z) | octaentsize, scale = worldscale-1; if(diff&~((1<= uint(worldsize)) { findents(worldroot, ivec(0, 0, 0), 1<ext && c->ext->ents) findents(*c->ext->ents, low, high, notspawned, pos, invradius, found); scale--; while(c->children && !(diff&(1<children[octastep(bo.x, bo.y, bo.z, scale)]; if(c->ext && c->ext->ents) findents(*c->ext->ents, low, high, notspawned, pos, invradius, found); scale--; } if(c->children && 1<= octaentsize) findents(c->children, ivec(bo).mask(~((2<= sel.o.x && o.y <= sel.o.y+sel.s.y*sel.grid && o.y >= sel.o.y && o.z <= sel.o.z+sel.s.z*sel.grid && o.z >= sel.o.z); } vector entgroup; bool haveselent() { return entgroup.length() > 0; } void entcancel() { entgroup.shrink(0); } static void entadd(int id) { undonext = true; entgroup.add(id); } static undoblock *newundoent() { int numents = entgroup.length(); if(numents <= 0) return NULL; undoblock *u = (undoblock *)new uchar[sizeof(undoblock) + numents*sizeof(undoent)]; u->numents = numents; undoent *e = (undoent *)(u + 1); loopv(entgroup) { e->i = entgroup[i]; e->e = *entities::getents()[entgroup[i]]; e++; } return u; } static void makeundoent() { if(!undonext) return; undonext = false; oldhover = enthover; undoblock *u = newundoent(); if(u) addundo(u); } static void detachentity(extentity &e) { if(!e.attached) return; e.attached->attached = NULL; e.attached = NULL; } VAR(attachradius, 1, 100, 1000); static void attachentity(extentity &e) { switch(e.type) { case ET_SPOTLIGHT: break; default: if(e.type &ents = entities::getents(); int closest = -1; float closedist = 1e10f; loopv(ents) { extentity *a = ents[i]; if(a->attached) continue; switch(e.type) { case ET_SPOTLIGHT: if(a->type!=ET_LIGHT) continue; break; default: if(e.typeo); if(dist < closedist) { closest = i; closedist = dist; } } if(closedist>attachradius) return; e.attached = ents[closest]; ents[closest]->attached = &e; } void attachentities() { vector &ents = entities::getents(); loopv(ents) attachentity(*ents[i]); } // convenience macros implicitly define: // e entity, currently edited ent // n int, index to currently edited ent #define addimplicit(f) { if(entgroup.empty() && enthover>=0) { entadd(enthover); undonext = (enthover != oldhover); f; entgroup.drop(); } else f; } #define entfocusv(i, f, v){ int n = efocus = (i); if(n>=0) { extentity &e = *v[n]; f; } } #define entfocus(i, f) entfocusv(i, f, entities::getents()) #define enteditv(i, f, v) \ { \ entfocusv(i, \ { \ int oldtype = e.type; \ removeentityedit(n); \ f; \ if(oldtype!=e.type) detachentity(e); \ if(e.type!=ET_EMPTY) { addentityedit(n); if(oldtype!=e.type) attachentity(e); } \ entities::editent(n, true); \ clearshadowcache(); \ }, v); \ } #define entedit(i, f) enteditv(i, f, entities::getents()) #define addgroup(exp) { vector &ents = entities::getents(); loopv(ents) entfocusv(i, if(exp) entadd(n), ents); } #define setgroup(exp) { entcancel(); addgroup(exp); } #define groupeditloop(f){ vector &ents = entities::getents(); entlooplevel++; int _ = efocus; loopv(entgroup) enteditv(entgroup[i], f, ents); efocus = _; entlooplevel--; } #define groupeditpure(f){ if(entlooplevel>0) { entedit(efocus, f); } else { groupeditloop(f); commitchanges(); } } #define groupeditundo(f){ makeundoent(); groupeditpure(f); } #define groupedit(f) { addimplicit(groupeditundo(f)); } vec getselpos() { vector &ents = entities::getents(); if(entgroup.length() && ents.inrange(entgroup[0])) return ents[entgroup[0]]->o; if(ents.inrange(enthover)) return ents[enthover]->o; return vec(sel.o); } undoblock *copyundoents(undoblock *u) { entcancel(); undoent *e = u->ents(); loopi(u->numents) entadd(e[i].i); undoblock *c = newundoent(); loopi(u->numents) if(e[i].e.type==ET_EMPTY) entgroup.removeobj(e[i].i); return c; } void pasteundoent(int idx, const entity &ue) { if(idx < 0 || idx >= MAXENTS) return; vector &ents = entities::getents(); while(ents.length() < idx) ents.add(entities::newentity())->type = ET_EMPTY; int efocus = -1; entedit(idx, (entity &)e = ue); } void pasteundoents(undoblock *u) { undoent *ue = u->ents(); loopi(u->numents) pasteundoent(ue[i].i, ue[i].e); } static void entflip() { if(noentedit()) return; int d = dimension(sel.orient); float mid = sel.s[d]*sel.grid/2+sel.o[d]; groupeditundo(e.o[d] -= (e.o[d]-mid)*2); } static void entrotate(int *cw) { if(noentedit()) return; int d = dimension(sel.orient); int dd = (*cw<0) == dimcoord(sel.orient) ? R[d] : C[d]; float mid = sel.s[dd]*sel.grid/2+sel.o[dd]; vec s(sel.o.v); groupeditundo( e.o[dd] -= (e.o[dd]-mid)*2; e.o.sub(s); swap(e.o[R[d]], e.o[C[d]]); e.o.add(s); ); } void entselectionbox(const entity &e, vec &eo, vec &es) { model *m = NULL; const char *mname = entities::entmodel(e); if(mname && (m = loadmodel(mname))) { m->collisionbox(eo, es); if(es.x > es.y) es.y = es.x; else es.x = es.y; // square es.z = (es.z + eo.z + 1 + entselradius)/2; // enclose ent radius box and model box eo.x += e.o.x; eo.y += e.o.y; eo.z = e.o.z - entselradius + es.z; } else if(e.type == ET_MAPMODEL && (m = loadmapmodel(e.attr1))) { mmcollisionbox(e, m, eo, es); es.max(entselradius); eo.add(e.o); } else if(e.type == ET_DECAL) { DecalSlot &s = lookupdecalslot(e.attr1, false); decalboundbox(e, s, eo, es); es.max(entselradius); eo.add(e.o); } else { es = vec(entselradius); eo = e.o; } eo.sub(es); es.mul(2); } VAR(entselsnap, 0, 0, 1); VAR(entmovingshadow, 0, 1, 1); extern void boxs(int orient, vec o, const vec &s, float size); extern void boxs(int orient, vec o, const vec &s); extern void boxs3D(const vec &o, vec s, int g); extern bool editmoveplane(const vec &o, const vec &ray, int d, float off, vec &handle, vec &dest, bool first); int entmoving = 0; void entdrag(const vec &ray) { if(noentedit() || !haveselent()) return; float r = 0, c = 0; static vec dest, handle; vec eo, es; int d = dimension(entorient), dc= dimcoord(entorient); entfocus(entgroup.last(), entselectionbox(e, eo, es); if(!editmoveplane(e.o, ray, d, eo[d] + (dc ? es[d] : 0), handle, dest, entmoving==1)) return; ivec g(dest); int z = g[d]&(~(sel.grid-1)); g.add(sel.grid/2).mask(~(sel.grid-1)); g[d] = z; r = (entselsnap ? g[R[d]] : dest[R[d]]) - e.o[R[d]]; c = (entselsnap ? g[C[d]] : dest[C[d]]) - e.o[C[d]]; ); if(entmoving==1) makeundoent(); groupeditpure(e.o[R[d]] += r; e.o[C[d]] += c); entmoving = 2; } VAR(showentradius, 0, 1, 1); static void renderentring(const extentity &e, float radius, int axis) { if(radius <= 0) return; gle::defvertex(); gle::begin(GL_LINE_LOOP); loopi(15) { vec p(e.o); const vec2 &sc = sincos360[i*(360/15)]; p[axis>=2 ? 1 : 0] += radius*sc.x; p[axis>=1 ? 2 : 1] += radius*sc.y; gle::attrib(p); } xtraverts += gle::end(); } static void renderentsphere(const extentity &e, float radius) { if(radius <= 0) return; loopk(3) renderentring(e, radius, k); } static void renderentattachment(const extentity &e) { if(!e.attached) return; gle::defvertex(); gle::begin(GL_LINES); gle::attrib(e.o); gle::attrib(e.attached->o); xtraverts += gle::end(); } static void renderentarrow(const extentity &e, const vec &dir, float radius) { if(radius <= 0) return; float arrowsize = min(radius/8, 0.5f); vec target = vec(dir).mul(radius).add(e.o), arrowbase = vec(dir).mul(radius - arrowsize).add(e.o), spoke; spoke.orthogonal(dir); spoke.normalize(); spoke.mul(arrowsize); gle::defvertex(); gle::begin(GL_LINES); gle::attrib(e.o); gle::attrib(target); xtraverts += gle::end(); gle::begin(GL_TRIANGLE_FAN); gle::attrib(target); loopi(5) gle::attrib(vec(spoke).rotate(2*M_PI*i/4.0f, dir).add(arrowbase)); xtraverts += gle::end(); } static void renderentcone(const extentity &e, const vec &dir, float radius, float angle) { if(radius <= 0) return; vec spot = vec(dir).mul(radius*cosf(angle*RAD)).add(e.o), spoke; spoke.orthogonal(dir); spoke.normalize(); spoke.mul(radius*sinf(angle*RAD)); gle::defvertex(); gle::begin(GL_LINES); loopi(8) { gle::attrib(e.o); gle::attrib(vec(spoke).rotate(2*M_PI*i/8.0f, dir).add(spot)); } xtraverts += gle::end(); gle::begin(GL_LINE_LOOP); loopi(8) gle::attrib(vec(spoke).rotate(2*M_PI*i/8.0f, dir).add(spot)); xtraverts += gle::end(); } static void renderentbox(const extentity &e, const vec ¢er, const vec &radius, int yaw, int pitch, int roll) { matrix4x3 orient; orient.identity(); orient.settranslation(e.o); if(yaw) orient.rotate_around_z(sincosmod360(yaw)); if(pitch) orient.rotate_around_x(sincosmod360(pitch)); if(roll) orient.rotate_around_y(sincosmod360(-roll)); orient.translate(center); gle::defvertex(); vec front[4] = { vec(-radius.x, -radius.y, -radius.z), vec( radius.x, -radius.y, -radius.z), vec( radius.x, -radius.y, radius.z), vec(-radius.x, -radius.y, radius.z) }, back[4] = { vec(-radius.x, radius.y, -radius.z), vec( radius.x, radius.y, -radius.z), vec( radius.x, radius.y, radius.z), vec(-radius.x, radius.y, radius.z) }; loopi(4) { front[i] = orient.transform(front[i]); back[i] = orient.transform(back[i]); } gle::begin(GL_LINE_LOOP); loopi(4) gle::attrib(front[i]); xtraverts += gle::end(); gle::begin(GL_LINES); gle::attrib(front[0]); gle::attrib(front[2]); gle::attrib(front[1]); gle::attrib(front[3]); loopi(4) { gle::attrib(front[i]); gle::attrib(back[i]); } xtraverts += gle::end(); gle::begin(GL_LINE_LOOP); loopi(4) gle::attrib(back[i]); xtraverts += gle::end(); } static void renderentradius(extentity &e, bool color) { switch(e.type) { case ET_LIGHT: if(e.attr1 <= 0) break; if(color) gle::colorf(e.attr2/255.0f, e.attr3/255.0f, e.attr4/255.0f); renderentsphere(e, e.attr1); break; case ET_SPOTLIGHT: if(e.attached) { if(color) gle::colorf(0, 1, 1); float radius = e.attached->attr1; if(radius <= 0) break; vec dir = vec(e.o).sub(e.attached->o).normalize(); float angle = clamp(int(e.attr1), 1, 89); renderentattachment(e); renderentcone(*e.attached, dir, radius, angle); } break; case ET_SOUND: if(color) gle::colorf(0, 1, 1); renderentsphere(e, e.attr2); break; case ET_ENVMAP: { extern int envmapradius; if(color) gle::colorf(0, 1, 1); renderentsphere(e, e.attr1 ? max(0, min(10000, int(e.attr1))) : envmapradius); break; } case ET_MAPMODEL: { if(color) gle::colorf(0, 1, 1); entities::entradius(e, color); vec dir; vecfromyawpitch(e.attr2, e.attr3, 1, 0, dir); renderentarrow(e, dir, 4); break; } case ET_PLAYERSTART: { if(color) gle::colorf(0, 1, 1); entities::entradius(e, color); vec dir; vecfromyawpitch(e.attr1, 0, 1, 0, dir); renderentarrow(e, dir, 4); break; } case ET_DECAL: { if(color) gle::colorf(0, 1, 1); DecalSlot &s = lookupdecalslot(e.attr1, false); float size = max(float(e.attr5), 1.0f); renderentbox(e, vec(0, s.depth * size/2, 0), vec(size/2, s.depth * size/2, size/2), e.attr2, e.attr3, e.attr4); break; } default: if(e.type>=ET_GAMESPECIFIC) { if(color) gle::colorf(0, 1, 1); entities::entradius(e, color); } break; } } static void renderentbox(const vec &eo, vec es) { es.add(eo); // bottom quad gle::attrib(eo.x, eo.y, eo.z); gle::attrib(es.x, eo.y, eo.z); gle::attrib(es.x, eo.y, eo.z); gle::attrib(es.x, es.y, eo.z); gle::attrib(es.x, es.y, eo.z); gle::attrib(eo.x, es.y, eo.z); gle::attrib(eo.x, es.y, eo.z); gle::attrib(eo.x, eo.y, eo.z); // top quad gle::attrib(eo.x, eo.y, es.z); gle::attrib(es.x, eo.y, es.z); gle::attrib(es.x, eo.y, es.z); gle::attrib(es.x, es.y, es.z); gle::attrib(es.x, es.y, es.z); gle::attrib(eo.x, es.y, es.z); gle::attrib(eo.x, es.y, es.z); gle::attrib(eo.x, eo.y, es.z); // sides gle::attrib(eo.x, eo.y, eo.z); gle::attrib(eo.x, eo.y, es.z); gle::attrib(es.x, eo.y, eo.z); gle::attrib(es.x, eo.y, es.z); gle::attrib(es.x, es.y, eo.z); gle::attrib(es.x, es.y, es.z); gle::attrib(eo.x, es.y, eo.z); gle::attrib(eo.x, es.y, es.z); } void renderentselection(const vec &o, const vec &ray, bool entmoving) { if(noentedit() || (entgroup.empty() && enthover < 0)) return; vec eo, es; if(entgroup.length()) { gle::colorub(0, 40, 0); gle::defvertex(); gle::begin(GL_LINES, entgroup.length()*24); loopv(entgroup) entfocus(entgroup[i], entselectionbox(e, eo, es); renderentbox(eo, es); ); xtraverts += gle::end(); } if(enthover >= 0) { gle::colorub(0, 40, 0); entfocus(enthover, entselectionbox(e, eo, es)); // also ensures enthover is back in focus boxs3D(eo, es, 1); if(entmoving && entmovingshadow==1) { vec a, b; gle::colorub(20, 20, 20); (a = eo).x = eo.x - fmod(eo.x, worldsize); (b = es).x = a.x + worldsize; boxs3D(a, b, 1); (a = eo).y = eo.y - fmod(eo.y, worldsize); (b = es).y = a.x + worldsize; boxs3D(a, b, 1); (a = eo).z = eo.z - fmod(eo.z, worldsize); (b = es).z = a.x + worldsize; boxs3D(a, b, 1); } gle::colorub(200,0,0); boxs(entorient, eo, es); boxs(entorient, eo, es, clamp(0.015f*camera1->o.dist(eo)*tan(fovy*0.5f*RAD), 0.1f, 1.0f)); } if(showentradius) { glDepthFunc(GL_GREATER); gle::colorf(0.25f, 0.25f, 0.25f); loopv(entgroup) entfocus(entgroup[i], renderentradius(e, false)); if(enthover>=0) entfocus(enthover, renderentradius(e, false)); glDepthFunc(GL_LESS); loopv(entgroup) entfocus(entgroup[i], renderentradius(e, true)); if(enthover>=0) entfocus(enthover, renderentradius(e, true)); } } static bool enttoggle(int id) { undonext = true; int i = entgroup.find(id); if(i < 0) entadd(id); else entgroup.remove(i); return i < 0; } bool hoveringonent(int ent, int orient) { if(noentedit()) return false; entorient = orient; if((efocus = enthover = ent) >= 0) return true; efocus = entgroup.empty() ? -1 : entgroup.last(); enthover = -1; return false; } VAR(entitysurf, 0, 0, 1); ICOMMAND(entadd, "", (), { if(enthover >= 0 && !noentedit()) { if(entgroup.find(enthover) < 0) entadd(enthover); if(entmoving > 1) entmoving = 1; } }); ICOMMAND(enttoggle, "", (), { if(enthover < 0 || noentedit() || !enttoggle(enthover)) { entmoving = 0; intret(0); } else { if(entmoving > 1) entmoving = 1; intret(1); } }); ICOMMAND(entmoving, "b", (int *n), { if(*n >= 0) { if(!*n || enthover < 0 || noentedit()) entmoving = 0; else { if(entgroup.find(enthover) < 0) { entadd(enthover); entmoving = 1; } else if(!entmoving) entmoving = 1; } } intret(entmoving); }); static void entpush(int *dir) { if(noentedit()) return; int d = dimension(entorient); int s = dimcoord(entorient) ? -*dir : *dir; if(entmoving) { groupeditpure(e.o[d] += float(s*sel.grid)); // editdrag supplies the undo } else groupedit(e.o[d] += float(s*sel.grid)); if(entitysurf==1) { player->o[d] += float(s*sel.grid); player->resetinterp(); } } VAR(entautoviewdist, 0, 25, 100); static void entautoview(int *dir) { if(!haveselent()) return; static int s = 0; vec v(player->o); v.sub(worldpos); v.normalize(); v.mul(entautoviewdist); int t = s + *dir; s = abs(t) % entgroup.length(); if(t<0 && s>0) s = entgroup.length() - s; entfocus(entgroup[s], v.add(e.o); player->o = v; player->resetinterp(); ); } COMMAND(entautoview, "i"); COMMAND(entflip, ""); COMMAND(entrotate, "i"); COMMAND(entpush, "i"); static void delent() { if(noentedit()) return; groupedit(e.type = ET_EMPTY;); entcancel(); } static int findtype(char *what) { for(int i = 0; *entities::entname(i); i++) if(strcmp(what, entities::entname(i))==0) return i; conoutf(CON_ERROR, "unknown entity type \"%s\"", what); return ET_EMPTY; } VAR(entdrop, 0, 2, 3); static bool dropentity(entity &e, int drop = -1) { vec radius(4.0f, 4.0f, 4.0f); if(drop<0) drop = entdrop; if(e.type == ET_MAPMODEL) { model *m = loadmapmodel(e.attr1); if(m) { vec center; mmboundbox(e, m, center, radius); radius.x += fabs(center.x); radius.y += fabs(center.y); } radius.z = 0.0f; } switch(drop) { case 1: if(e.type != ET_LIGHT && e.type != ET_SPOTLIGHT) dropenttofloor(&e); break; case 2: case 3: int cx = 0, cy = 0; if(sel.cxs == 1 && sel.cys == 1) { cx = (sel.cx ? 1 : -1) * sel.grid / 2; cy = (sel.cy ? 1 : -1) * sel.grid / 2; } e.o = vec(sel.o); int d = dimension(sel.orient), dc = dimcoord(sel.orient); e.o[R[d]] += sel.grid / 2 + cx; e.o[C[d]] += sel.grid / 2 + cy; if(!dc) e.o[D[d]] -= radius[D[d]]; else e.o[D[d]] += sel.grid + radius[D[d]]; if(drop == 3) dropenttofloor(&e); break; } return true; } static void dropent() { if(noentedit()) return; groupedit(dropentity(e)); } static void attachent() { if(noentedit()) return; groupedit(attachentity(e)); } COMMAND(attachent, ""); static int keepents = 0; static extentity *newentity(bool local, const vec &o, int type, int v1, int v2, int v3, int v4, int v5, int &idx, bool fix = true) { vector &ents = entities::getents(); if(local) { idx = -1; for(int i = keepents; i < ents.length(); i++) if(ents[i]->type == ET_EMPTY) { idx = i; break; } if(idx < 0 && ents.length() >= MAXENTS) { conoutf("too many entities"); return NULL; } } else while(ents.length() < idx) ents.add(entities::newentity())->type = ET_EMPTY; extentity &e = *entities::newentity(); e.o = o; e.attr1 = v1; e.attr2 = v2; e.attr3 = v3; e.attr4 = v4; e.attr5 = v5; e.type = type; e.reserved = 0; if(local && fix) { switch(type) { case ET_DECAL: if(!e.attr2 && !e.attr3 && !e.attr4) { e.attr2 = (int)camera1->yaw; e.attr3 = (int)camera1->pitch; e.attr4 = (int)camera1->roll; } break; case ET_MAPMODEL: if(!e.attr2) e.attr2 = (int)camera1->yaw; break; case ET_PLAYERSTART: e.attr5 = e.attr4; e.attr4 = e.attr3; e.attr3 = e.attr2; e.attr2 = e.attr1; e.attr1 = (int)camera1->yaw; break; } entities::fixentity(e); } if(ents.inrange(idx)) { entities::deleteentity(ents[idx]); ents[idx] = &e; } else { idx = ents.length(); ents.add(&e); } return &e; } static void newentity(int type, int a1, int a2, int a3, int a4, int a5, bool fix = true) { int idx; extentity *t = newentity(true, player->o, type, a1, a2, a3, a4, a5, idx, fix); if(!t) return; dropentity(*t); t->type = ET_EMPTY; enttoggle(idx); makeundoent(); entedit(idx, e.type = type); commitchanges(); } static void newent(char *what, int *a1, int *a2, int *a3, int *a4, int *a5) { if(noentedit()) return; int type = findtype(what); if(type != ET_EMPTY) newentity(type, *a1, *a2, *a3, *a4, *a5); } static int entcopygrid; static vector entcopybuf; static void entcopy() { if(noentedit()) return; entcopygrid = sel.grid; entcopybuf.shrink(0); addimplicit({ loopv(entgroup) entfocus(entgroup[i], entcopybuf.add(e).o.sub(vec(sel.o))); }); } static void entpaste() { if(noentedit() || entcopybuf.empty()) return; entcancel(); float m = float(sel.grid)/float(entcopygrid); loopv(entcopybuf) { const entity &c = entcopybuf[i]; vec o = vec(c.o).mul(m).add(vec(sel.o)); int idx; extentity *e = newentity(true, o, ET_EMPTY, c.attr1, c.attr2, c.attr3, c.attr4, c.attr5, idx); if(!e) continue; entadd(idx); keepents = max(keepents, idx+1); } keepents = 0; int j = 0; groupeditundo(e.type = entcopybuf[j++].type;); } static void entreplace() { if(noentedit() || entcopybuf.empty()) return; const entity &c = entcopybuf[0]; if(entgroup.length() || enthover >= 0) { groupedit({ e.type = c.type; e.attr1 = c.attr1; e.attr2 = c.attr2; e.attr3 = c.attr3; e.attr4 = c.attr4; e.attr5 = c.attr5; }); } else { newentity(c.type, c.attr1, c.attr2, c.attr3, c.attr4, c.attr5, false); } } COMMAND(newent, "siiiii"); COMMAND(delent, ""); COMMAND(dropent, ""); COMMAND(entcopy, ""); COMMAND(entpaste, ""); COMMAND(entreplace, ""); static void entset(char *what, int *a1, int *a2, int *a3, int *a4, int *a5) { if(noentedit()) return; int type = findtype(what); if(type != ET_EMPTY) groupedit(e.type=type; e.attr1=*a1; e.attr2=*a2; e.attr3=*a3; e.attr4=*a4; e.attr5=*a5); } static void printent(extentity &e, char *buf, int len) { switch(e.type) { case ET_PARTICLES: if(printparticles(e, buf, len)) return; break; default: if(e.type >= ET_GAMESPECIFIC && entities::printent(e, buf, len)) return; break; } nformatstring(buf, len, "%s %d %d %d %d %d", entities::entname(e.type), e.attr1, e.attr2, e.attr3, e.attr4, e.attr5); } static void nearestent() { if(noentedit()) return; int closest = -1; float closedist = 1e16f; vector &ents = entities::getents(); loopv(ents) { extentity &e = *ents[i]; if(e.type == ET_EMPTY) continue; float dist = e.o.dist(player->o); if(dist < closedist) { closest = i; closedist = dist; } } if(closest >= 0 && entgroup.find(closest) < 0) entadd(closest); } ICOMMAND(enthavesel,"", (), addimplicit(intret(entgroup.length()))); ICOMMAND(entselect, "e", (uint *body), if(!noentedit()) addgroup(e.type != ET_EMPTY && entgroup.find(n)<0 && executebool(body))); ICOMMAND(entloop, "e", (uint *body), if(!noentedit()) addimplicit(groupeditloop(((void)e, execute(body))))); ICOMMAND(insel, "", (), entfocus(efocus, intret(pointinsel(sel, e.o)))); ICOMMAND(entget, "", (), entfocus(efocus, string s; printent(e, s, sizeof(s)); result(s))); ICOMMAND(entindex, "", (), intret(efocus)); COMMAND(entset, "siiiii"); COMMAND(nearestent, ""); static void enttype(char *type, int *numargs) { if(*numargs >= 1) { int typeidx = findtype(type); if(typeidx != ET_EMPTY) groupedit(e.type = typeidx); } else entfocus(efocus, { result(entities::entname(e.type)); }) } static void entattr(int *attr, int *val, int *numargs) { if(*numargs >= 2) { if(*attr >= 0 && *attr <= 4) groupedit( switch(*attr) { case 0: e.attr1 = *val; break; case 1: e.attr2 = *val; break; case 2: e.attr3 = *val; break; case 3: e.attr4 = *val; break; case 4: e.attr5 = *val; break; } ); } else entfocus(efocus, { switch(*attr) { case 0: intret(e.attr1); break; case 1: intret(e.attr2); break; case 2: intret(e.attr3); break; case 3: intret(e.attr4); break; case 4: intret(e.attr5); break; } }); } COMMAND(enttype, "sN"); COMMAND(entattr, "iiN"); static void splitocta(cube *c, int size) { if(size <= 0x1000) return; loopi(8) { if(!c[i].children) c[i].children = newcubes(isempty(c[i]) ? F_EMPTY : F_SOLID); splitocta(c[i].children, size>>1); } } void resetmap() { clearoverrides(); //clearmapsounds(); resetblendmap(); clearlights(); clearpvs(); clearslots(); clearparticles(); clearstains(); clearsleep(); cancelsel(); pruneundos(); clearmapcrc(); entities::clearents(); outsideents.setsize(0); spotlights = 0; volumetriclights = 0; nospeclights = 0; } void startmap(const char *name) { game::startmap(name); } bool emptymap(int scale, bool force, const char *mname, bool usecfg) // main empty world creation routine { if(!force && !editmode) { conoutf(CON_ERROR, "newmap only allowed in edit mode"); return false; } resetmap(); setvar("mapscale", scale<10 ? 10 : (scale>16 ? 16 : scale), true, false); setvar("mapsize", 1< 0x1000) splitocta(worldroot, worldsize>>1); clearmainmenu(); if(usecfg) { identflags |= IDF_OVERRIDDEN; execfile("config/default_map_settings.cfg", false); identflags &= ~IDF_OVERRIDDEN; } allchanged(true); startmap(mname); return true; } bool enlargemap(bool force) { if(!force && !editmode) { conoutf(CON_ERROR, "mapenlarge only allowed in edit mode"); return false; } if(worldsize >= 1<<16) return false; while(outsideents.length()) removeentity(outsideents.pop()); worldscale++; worldsize *= 2; cube *c = newcubes(F_EMPTY); c[0].children = worldroot; loopi(3) solidfaces(c[i+1]); worldroot = c; if(worldsize > 0x1000) splitocta(worldroot, worldsize>>1); enlargeblendmap(); allchanged(); return true; } static bool isallempty(cube &c) { if(!c.children) return isempty(c); loopi(8) if(!isallempty(c.children[i])) return false; return true; } static void shrinkmap() { extern int nompedit; if(noedit(true) || (nompedit && multiplayer())) return; if(worldsize <= 1<<10) return; int octant = -1; loopi(8) if(!isallempty(worldroot[i])) { if(octant >= 0) return; octant = i; } if(octant < 0) return; while(outsideents.length()) removeentity(outsideents.pop()); if(!worldroot[octant].children) subdividecube(worldroot[octant], false, false); cube *root = worldroot[octant].children; worldroot[octant].children = NULL; freeocta(worldroot); worldroot = root; worldscale--; worldsize /= 2; ivec offset(octant, ivec(0, 0, 0), worldsize); vector &ents = entities::getents(); loopv(ents) ents[i]->o.sub(vec(offset)); shrinkblendmap(octant); allchanged(); conoutf("shrunk map to size %d", worldscale); } static void newmap(int *i) { bool force = !isconnected(); if(force) game::forceedit(""); if(emptymap(*i, force, NULL)) game::newmap(max(*i, 0)); } static void mapenlarge() { if(enlargemap(false)) game::newmap(-1); } COMMAND(newmap, "i"); COMMAND(mapenlarge, ""); COMMAND(shrinkmap, ""); int getworldsize() { return worldsize; }