1 // world.cpp: core map management stuff
2 
3 #include "engine.h"
4 
5 VARR(mapversion, 1, MAPVERSION, 0);
6 VARNR(mapscale, worldscale, 1, 0, 0);
7 VARNR(mapsize, worldsize, 1, 0, 0);
8 SVARR(maptitle, "Untitled Map by Unknown");
9 VARNR(emptymap, _emptymap, 1, 0, 0);
10 
11 VAR(octaentsize, 0, 64, 1024);
12 VAR(entselradius, 0, 2, 10);
13 
transformbb(const entity & e,vec & center,vec & radius)14 static inline void transformbb(const entity &e, vec &center, vec &radius)
15 {
16     if(e.attr5 > 0) { float scale = e.attr5/100.0f; center.mul(scale); radius.mul(scale); }
17     rotatebb(center, radius, e.attr2, e.attr3, e.attr4);
18 }
19 
mmboundbox(const entity & e,model * m,vec & center,vec & radius)20 static inline void mmboundbox(const entity &e, model *m, vec &center, vec &radius)
21 {
22     m->boundbox(center, radius);
23     transformbb(e, center, radius);
24 }
25 
mmcollisionbox(const entity & e,model * m,vec & center,vec & radius)26 static inline void mmcollisionbox(const entity &e, model *m, vec &center, vec &radius)
27 {
28     m->collisionbox(center, radius);
29     transformbb(e, center, radius);
30 }
31 
decalboundbox(const entity & e,DecalSlot & s,vec & center,vec & radius)32 static inline void decalboundbox(const entity &e, DecalSlot &s, vec &center, vec &radius)
33 {
34     float size = max(float(e.attr5), 1.0f);
35     center = vec(0, s.depth * size/2, 0);
36     radius = vec(size/2, s.depth * size/2, size/2);
37     rotatebb(center, radius, e.attr2, e.attr3, e.attr4);
38 }
39 
getentboundingbox(const extentity & e,ivec & o,ivec & r)40 bool getentboundingbox(const extentity &e, ivec &o, ivec &r)
41 {
42     switch(e.type)
43     {
44         case ET_EMPTY:
45             return false;
46         case ET_DECAL:
47             {
48                 DecalSlot &s = lookupdecalslot(e.attr1, false);
49                 vec center, radius;
50                 decalboundbox(e, s, center, radius);
51                 center.add(e.o);
52                 radius.max(entselradius);
53                 o = vec(center).sub(radius);
54                 r = vec(center).add(radius).add(1);
55                 break;
56             }
57         case ET_MAPMODEL:
58             if(model *m = loadmapmodel(e.attr1))
59             {
60                 vec center, radius;
61                 mmboundbox(e, m, center, radius);
62                 center.add(e.o);
63                 radius.max(entselradius);
64                 o = vec(center).sub(radius);
65                 r = vec(center).add(radius).add(1);
66                 break;
67             }
68         // invisible mapmodels use entselradius
69         default:
70             o = vec(e.o).sub(entselradius);
71             r = vec(e.o).add(entselradius+1);
72             break;
73     }
74     return true;
75 }
76 
77 enum
78 {
79     MODOE_ADD      = 1<<0,
80     MODOE_UPDATEBB = 1<<1,
81     MODOE_CHANGED  = 1<<2
82 };
83 
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)84 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)
85 {
86     loopoctabox(cor, size, bo, br)
87     {
88         ivec o(i, cor, size);
89         vtxarray *va = c[i].ext && c[i].ext->va ? c[i].ext->va : lastva;
90         if(c[i].children != NULL && size > leafsize)
91             modifyoctaentity(flags, id, e, c[i].children, o, size>>1, bo, br, leafsize, va);
92         else if(flags&MODOE_ADD)
93         {
94             if(!c[i].ext || !c[i].ext->ents) ext(c[i]).ents = new octaentities(o, size);
95             octaentities &oe = *c[i].ext->ents;
96             switch(e.type)
97             {
98                 case ET_DECAL:
99                     if(va)
100                     {
101                         va->bbmin.x = -1;
102                         if(oe.decals.empty()) va->decals.add(&oe);
103                     }
104                     oe.decals.add(id);
105                     oe.bbmin.min(bo).max(oe.o);
106                     oe.bbmax.max(br).min(ivec(oe.o).add(oe.size));
107                     break;
108                 case ET_MAPMODEL:
109                     if(loadmapmodel(e.attr1))
110                     {
111                         if(va)
112                         {
113                             va->bbmin.x = -1;
114                             if(oe.mapmodels.empty()) va->mapmodels.add(&oe);
115                         }
116                         oe.mapmodels.add(id);
117                         oe.bbmin.min(bo).max(oe.o);
118                         oe.bbmax.max(br).min(ivec(oe.o).add(oe.size));
119                         break;
120                     }
121                     // invisible mapmodel
122                 default:
123                     oe.other.add(id);
124                     break;
125             }
126 
127         }
128         else if(c[i].ext && c[i].ext->ents)
129         {
130             octaentities &oe = *c[i].ext->ents;
131             switch(e.type)
132             {
133                 case ET_DECAL:
134                     oe.decals.removeobj(id);
135                     if(va)
136                     {
137                         va->bbmin.x = -1;
138                         if(oe.decals.empty()) va->decals.removeobj(&oe);
139                     }
140                     oe.bbmin = oe.bbmax = oe.o;
141                     oe.bbmin.add(oe.size);
142                     loopvj(oe.decals)
143                     {
144                         extentity &e = *entities::getents()[oe.decals[j]];
145                         ivec eo, er;
146                         if(getentboundingbox(e, eo, er))
147                         {
148                             oe.bbmin.min(eo);
149                             oe.bbmax.max(er);
150                         }
151                     }
152                     oe.bbmin.max(oe.o);
153                     oe.bbmax.min(ivec(oe.o).add(oe.size));
154                     break;
155                 case ET_MAPMODEL:
156                     if(loadmapmodel(e.attr1))
157                     {
158                         oe.mapmodels.removeobj(id);
159                         if(va)
160                         {
161                             va->bbmin.x = -1;
162                             if(oe.mapmodels.empty()) va->mapmodels.removeobj(&oe);
163                         }
164                         oe.bbmin = oe.bbmax = oe.o;
165                         oe.bbmin.add(oe.size);
166                         loopvj(oe.mapmodels)
167                         {
168                             extentity &e = *entities::getents()[oe.mapmodels[j]];
169                             ivec eo, er;
170                             if(getentboundingbox(e, eo, er))
171                             {
172                                 oe.bbmin.min(eo);
173                                 oe.bbmax.max(er);
174                             }
175                         }
176                         oe.bbmin.max(oe.o);
177                         oe.bbmax.min(ivec(oe.o).add(oe.size));
178                         break;
179                     }
180                     // invisible mapmodel
181                 default:
182                     oe.other.removeobj(id);
183                     break;
184             }
185             if(oe.mapmodels.empty() && oe.decals.empty() && oe.other.empty())
186                 freeoctaentities(c[i]);
187         }
188         if(c[i].ext && c[i].ext->ents) c[i].ext->ents->query = NULL;
189         if(va && va!=lastva)
190         {
191             if(lastva)
192             {
193                 if(va->bbmin.x < 0) lastva->bbmin.x = -1;
194             }
195             else if(flags&MODOE_UPDATEBB) updatevabb(va);
196         }
197     }
198 }
199 
200 vector<int> outsideents;
201 int spotlights = 0;
202 
modifyoctaent(int flags,int id,extentity & e)203 static bool modifyoctaent(int flags, int id, extentity &e)
204 {
205     if(flags&MODOE_ADD ? e.flags&EF_OCTA : !(e.flags&EF_OCTA)) return false;
206 
207     ivec o, r;
208     if(!getentboundingbox(e, o, r)) return false;
209 
210     if(!insideworld(e.o))
211     {
212         int idx = outsideents.find(id);
213         if(flags&MODOE_ADD)
214         {
215             if(idx < 0) outsideents.add(id);
216         }
217         else if(idx >= 0) outsideents.removeunordered(idx);
218     }
219     else
220     {
221         int leafsize = octaentsize, limit = max(r.x - o.x, max(r.y - o.y, r.z - o.z));
222         while(leafsize < limit) leafsize *= 2;
223         int diff = ~(leafsize-1) & ((o.x^r.x)|(o.y^r.y)|(o.z^r.z));
224         if(diff && (limit > octaentsize/2 || diff < leafsize*2)) leafsize *= 2;
225         modifyoctaentity(flags, id, e, worldroot, ivec(0, 0, 0), worldsize>>1, o, r, leafsize);
226     }
227     e.flags ^= EF_OCTA;
228     switch(e.type)
229     {
230         case ET_LIGHT: clearlightcache(id); break;
231         case ET_SPOTLIGHT: if(!(flags&MODOE_ADD ? spotlights++ : --spotlights)) cleardeferredlightshaders();
232         case ET_PARTICLES: clearparticleemitters(); break;
233         case ET_DECAL: if(flags&MODOE_CHANGED) changed(o, r, false); break;
234     }
235     return true;
236 }
237 
modifyoctaent(int flags,int id)238 static inline bool modifyoctaent(int flags, int id)
239 {
240     vector<extentity *> &ents = entities::getents();
241     return ents.inrange(id) && modifyoctaent(flags, id, *ents[id]);
242 }
243 
addentity(int id)244 static inline void addentity(int id)        { modifyoctaent(MODOE_ADD|MODOE_UPDATEBB, id); }
addentityedit(int id)245 static inline void addentityedit(int id)    { modifyoctaent(MODOE_ADD|MODOE_UPDATEBB|MODOE_CHANGED, id); }
removeentity(int id)246 static inline void removeentity(int id)     { modifyoctaent(MODOE_UPDATEBB, id); }
removeentityedit(int id)247 static inline void removeentityedit(int id) { modifyoctaent(MODOE_UPDATEBB|MODOE_CHANGED, id); }
248 
freeoctaentities(cube & c)249 void freeoctaentities(cube &c)
250 {
251     if(!c.ext) return;
252     if(entities::getents().length())
253     {
254         while(c.ext->ents && !c.ext->ents->mapmodels.empty()) removeentity(c.ext->ents->mapmodels.pop());
255         while(c.ext->ents && !c.ext->ents->decals.empty())    removeentity(c.ext->ents->decals.pop());
256         while(c.ext->ents && !c.ext->ents->other.empty())     removeentity(c.ext->ents->other.pop());
257     }
258     if(c.ext->ents)
259     {
260         delete c.ext->ents;
261         c.ext->ents = NULL;
262     }
263 }
264 
entitiesinoctanodes()265 void entitiesinoctanodes()
266 {
267     vector<extentity *> &ents = entities::getents();
268     loopv(ents) modifyoctaent(MODOE_ADD, i, *ents[i]);
269 }
270 
findents(octaentities & oe,int low,int high,bool notspawned,const vec & pos,const vec & radius,vector<int> & found)271 static inline void findents(octaentities &oe, int low, int high, bool notspawned, const vec &pos, const vec &radius, vector<int> &found)
272 {
273     vector<extentity *> &ents = entities::getents();
274     loopv(oe.other)
275     {
276         int id = oe.other[i];
277         extentity &e = *ents[id];
278         if(e.type >= low && e.type <= high && (e.spawned() || notspawned) && vec(e.o).mul(radius).squaredlen() <= 1) found.add(id);
279     }
280 }
281 
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 & radius,vector<int> & found)282 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 &radius, vector<int> &found)
283 {
284     loopoctabox(o, size, bo, br)
285     {
286         if(c[i].ext && c[i].ext->ents) findents(*c[i].ext->ents, low, high, notspawned, pos, radius, found);
287         if(c[i].children && size > octaentsize)
288         {
289             ivec co(i, o, size);
290             findents(c[i].children, co, size>>1, bo, br, low, high, notspawned, pos, radius, found);
291         }
292     }
293 }
294 
findents(int low,int high,bool notspawned,const vec & pos,const vec & radius,vector<int> & found)295 void findents(int low, int high, bool notspawned, const vec &pos, const vec &radius, vector<int> &found)
296 {
297     vec invradius(1/radius.x, 1/radius.y, 1/radius.z);
298     ivec bo = vec(pos).sub(radius).sub(1),
299          br = vec(pos).add(radius).add(1);
300     int diff = (bo.x^br.x) | (bo.y^br.y) | (bo.z^br.z) | octaentsize,
301         scale = worldscale-1;
302     if(diff&~((1<<scale)-1) || uint(bo.x|bo.y|bo.z|br.x|br.y|br.z) >= uint(worldsize))
303     {
304         findents(worldroot, ivec(0, 0, 0), 1<<scale, bo, br, low, high, notspawned, pos, invradius, found);
305         return;
306     }
307     cube *c = &worldroot[octastep(bo.x, bo.y, bo.z, scale)];
308     if(c->ext && c->ext->ents) findents(*c->ext->ents, low, high, notspawned, pos, invradius, found);
309     scale--;
310     while(c->children && !(diff&(1<<scale)))
311     {
312         c = &c->children[octastep(bo.x, bo.y, bo.z, scale)];
313         if(c->ext && c->ext->ents) findents(*c->ext->ents, low, high, notspawned, pos, invradius, found);
314         scale--;
315     }
316     if(c->children && 1<<scale >= octaentsize) findents(c->children, ivec(bo).mask(~((2<<scale)-1)), 1<<scale, bo, br, low, high, notspawned, pos, invradius, found);
317 }
318 
entname(entity & e)319 char *entname(entity &e)
320 {
321     static string fullentname;
322     copystring(fullentname, entities::entname(e.type));
323     const char *einfo = entities::entnameinfo(e);
324     if(*einfo)
325     {
326         concatstring(fullentname, ": ");
327         concatstring(fullentname, einfo);
328     }
329     return fullentname;
330 }
331 
332 extern selinfo sel;
333 extern bool havesel;
334 int entlooplevel = 0;
335 int efocus = -1, enthover = -1, entorient = -1, oldhover = -1;
336 bool undonext = true;
337 
338 VARF(entediting, 0, 0, 1, { if(!entediting) { entcancel(); efocus = enthover = -1; } });
339 
noentedit()340 bool noentedit()
341 {
342     if(!editmode) { conoutf(CON_ERROR, "operation only allowed in edit mode"); return true; }
343     return !entediting;
344 }
345 
pointinsel(const selinfo & sel,const vec & o)346 bool pointinsel(const selinfo &sel, const vec &o)
347 {
348     return(o.x <= sel.o.x+sel.s.x*sel.grid
349         && o.x >= sel.o.x
350         && o.y <= sel.o.y+sel.s.y*sel.grid
351         && o.y >= sel.o.y
352         && o.z <= sel.o.z+sel.s.z*sel.grid
353         && o.z >= sel.o.z);
354 }
355 
356 vector<int> entgroup;
357 
haveselent()358 bool haveselent()
359 {
360     return entgroup.length() > 0;
361 }
362 
entcancel()363 void entcancel()
364 {
365     entgroup.shrink(0);
366 }
367 
entadd(int id)368 void entadd(int id)
369 {
370     undonext = true;
371     entgroup.add(id);
372 }
373 
newundoent()374 undoblock *newundoent()
375 {
376     int numents = entgroup.length();
377     if(numents <= 0) return NULL;
378     undoblock *u = (undoblock *)new uchar[sizeof(undoblock) + numents*sizeof(undoent)];
379     u->numents = numents;
380     undoent *e = (undoent *)(u + 1);
381     loopv(entgroup)
382     {
383         e->i = entgroup[i];
384         e->e = *entities::getents()[entgroup[i]];
385         e++;
386     }
387     return u;
388 }
389 
makeundoent()390 void makeundoent()
391 {
392     if(!undonext) return;
393     undonext = false;
394     oldhover = enthover;
395     undoblock *u = newundoent();
396     if(u) addundo(u);
397 }
398 
detachentity(extentity & e)399 void detachentity(extentity &e)
400 {
401     if(!e.attached) return;
402     e.attached->attached = NULL;
403     e.attached = NULL;
404 }
405 
406 VAR(attachradius, 1, 100, 1000);
407 
attachentity(extentity & e)408 void attachentity(extentity &e)
409 {
410     switch(e.type)
411     {
412         case ET_SPOTLIGHT:
413             break;
414 
415         default:
416             if(e.type<ET_GAMESPECIFIC || !entities::mayattach(e)) return;
417             break;
418     }
419 
420     detachentity(e);
421 
422     vector<extentity *> &ents = entities::getents();
423     int closest = -1;
424     float closedist = 1e10f;
425     loopv(ents)
426     {
427         extentity *a = ents[i];
428         if(a->attached) continue;
429         switch(e.type)
430         {
431             case ET_SPOTLIGHT:
432                 if(a->type!=ET_LIGHT) continue;
433                 break;
434 
435             default:
436                 if(e.type<ET_GAMESPECIFIC || !entities::attachent(e, *a)) continue;
437                 break;
438         }
439         float dist = e.o.dist(a->o);
440         if(dist < closedist)
441         {
442             closest = i;
443             closedist = dist;
444         }
445     }
446     if(closedist>attachradius) return;
447     e.attached = ents[closest];
448     ents[closest]->attached = &e;
449 }
450 
attachentities()451 void attachentities()
452 {
453     vector<extentity *> &ents = entities::getents();
454     loopv(ents) attachentity(*ents[i]);
455 }
456 
457 // convenience macros implicitly define:
458 // e         entity, currently edited ent
459 // n         int,    index to currently edited ent
460 #define addimplicit(f)    { if(entgroup.empty() && enthover>=0) { entadd(enthover); undonext = (enthover != oldhover); f; entgroup.drop(); } else f; }
461 #define entfocusv(i, f, v){ int n = efocus = (i); if(n>=0) { extentity &e = *v[n]; f; } }
462 #define entfocus(i, f)    entfocusv(i, f, entities::getents())
463 #define enteditv(i, f, v) \
464 { \
465     entfocusv(i, \
466     { \
467         int oldtype = e.type; \
468         removeentityedit(n);  \
469         f; \
470         if(oldtype!=e.type) detachentity(e); \
471         if(e.type!=ET_EMPTY) { addentityedit(n); if(oldtype!=e.type) attachentity(e); } \
472         entities::editent(n, true); \
473         clearshadowcache(); \
474     }, v); \
475 }
476 #define entedit(i, f)   enteditv(i, f, entities::getents())
477 #define addgroup(exp)   { vector<extentity *> &ents = entities::getents(); loopv(ents) entfocusv(i, if(exp) entadd(n), ents); }
478 #define setgroup(exp)   { entcancel(); addgroup(exp); }
479 #define groupeditloop(f){ vector<extentity *> &ents = entities::getents(); entlooplevel++; int _ = efocus; loopv(entgroup) enteditv(entgroup[i], f, ents); efocus = _; entlooplevel--; }
480 #define groupeditpure(f){ if(entlooplevel>0) { entedit(efocus, f); } else { groupeditloop(f); commitchanges(); } }
481 #define groupeditundo(f){ makeundoent(); groupeditpure(f); }
482 #define groupedit(f)    { addimplicit(groupeditundo(f)); }
483 
getselpos()484 vec getselpos()
485 {
486     vector<extentity *> &ents = entities::getents();
487     if(entgroup.length() && ents.inrange(entgroup[0])) return ents[entgroup[0]]->o;
488     if(ents.inrange(enthover)) return ents[enthover]->o;
489     return vec(sel.o);
490 }
491 
copyundoents(undoblock * u)492 undoblock *copyundoents(undoblock *u)
493 {
494     entcancel();
495     undoent *e = u->ents();
496     loopi(u->numents)
497         entadd(e[i].i);
498     undoblock *c = newundoent();
499     loopi(u->numents) if(e[i].e.type==ET_EMPTY)
500         entgroup.removeobj(e[i].i);
501     return c;
502 }
503 
pasteundoents(undoblock * u)504 void pasteundoents(undoblock *u)
505 {
506     undoent *ue = u->ents();
507     loopi(u->numents)
508         entedit(ue[i].i, (entity &)e = ue[i].e);
509 }
510 
entflip()511 void entflip()
512 {
513     if(noentedit()) return;
514     int d = dimension(sel.orient);
515     float mid = sel.s[d]*sel.grid/2+sel.o[d];
516     groupeditundo(e.o[d] -= (e.o[d]-mid)*2);
517 }
518 
entrotate(int * cw)519 void entrotate(int *cw)
520 {
521     if(noentedit()) return;
522     int d = dimension(sel.orient);
523     int dd = (*cw<0) == dimcoord(sel.orient) ? R[d] : C[d];
524     float mid = sel.s[dd]*sel.grid/2+sel.o[dd];
525     vec s(sel.o.v);
526     groupeditundo(
527         e.o[dd] -= (e.o[dd]-mid)*2;
528         e.o.sub(s);
529         swap(e.o[R[d]], e.o[C[d]]);
530         e.o.add(s);
531     );
532 }
533 
entselectionbox(const entity & e,vec & eo,vec & es)534 void entselectionbox(const entity &e, vec &eo, vec &es)
535 {
536     model *m = NULL;
537     const char *mname = entities::entmodel(e);
538     if(mname && (m = loadmodel(mname)))
539     {
540         m->collisionbox(eo, es);
541         if(es.x > es.y) es.y = es.x; else es.x = es.y; // square
542         es.z = (es.z + eo.z + 1 + entselradius)/2; // enclose ent radius box and model box
543         eo.x += e.o.x;
544         eo.y += e.o.y;
545         eo.z = e.o.z - entselradius + es.z;
546     }
547     else if(e.type == ET_MAPMODEL && (m = loadmapmodel(e.attr1)))
548     {
549         mmcollisionbox(e, m, eo, es);
550         es.max(entselradius);
551         eo.add(e.o);
552     }
553     else if(e.type == ET_DECAL)
554     {
555         DecalSlot &s = lookupdecalslot(e.attr1, false);
556         decalboundbox(e, s, eo, es);
557         es.max(entselradius);
558         eo.add(e.o);
559     }
560     else
561     {
562         es = vec(entselradius);
563         eo = e.o;
564     }
565     eo.sub(es);
566     es.mul(2);
567 }
568 
569 VAR(entselsnap, 0, 0, 1);
570 VAR(entmovingshadow, 0, 1, 1);
571 
572 extern void boxs(int orient, vec o, const vec &s);
573 extern void boxs3D(const vec &o, vec s, int g);
574 extern bool editmoveplane(const vec &o, const vec &ray, int d, float off, vec &handle, vec &dest, bool first);
575 
576 int entmoving = 0;
577 
entdrag(const vec & ray)578 void entdrag(const vec &ray)
579 {
580     if(noentedit() || !haveselent()) return;
581 
582     float r = 0, c = 0;
583     static vec dest, handle;
584     vec eo, es;
585     int d = dimension(entorient),
586         dc= dimcoord(entorient);
587 
588     entfocus(entgroup.last(),
589         entselectionbox(e, eo, es);
590 
591         if(!editmoveplane(e.o, ray, d, eo[d] + (dc ? es[d] : 0), handle, dest, entmoving==1))
592             return;
593 
594         ivec g = dest;
595         int z = g[d]&(~(sel.grid-1));
596         g.add(sel.grid/2).mask(~(sel.grid-1));
597         g[d] = z;
598 
599         r = (entselsnap ? g[R[d]] : dest[R[d]]) - e.o[R[d]];
600         c = (entselsnap ? g[C[d]] : dest[C[d]]) - e.o[C[d]];
601     );
602 
603     if(entmoving==1) makeundoent();
604     groupeditpure(e.o[R[d]] += r; e.o[C[d]] += c);
605     entmoving = 2;
606 }
607 
608 VAR(showentradius, 0, 1, 1);
609 
renderentring(const extentity & e,float radius,int axis)610 void renderentring(const extentity &e, float radius, int axis)
611 {
612     if(radius <= 0) return;
613     gle::defvertex();
614     gle::begin(GL_LINE_LOOP);
615     loopi(15)
616     {
617         vec p(e.o);
618         const vec2 &sc = sincos360[i*(360/15)];
619         p[axis>=2 ? 1 : 0] += radius*sc.x;
620         p[axis>=1 ? 2 : 1] += radius*sc.y;
621         gle::attrib(p);
622     }
623     xtraverts += gle::end();
624 }
625 
renderentsphere(const extentity & e,float radius)626 void renderentsphere(const extentity &e, float radius)
627 {
628     if(radius <= 0) return;
629     loopk(3) renderentring(e, radius, k);
630 }
631 
renderentattachment(const extentity & e)632 void renderentattachment(const extentity &e)
633 {
634     if(!e.attached) return;
635     gle::defvertex();
636     gle::begin(GL_LINES);
637     gle::attrib(e.o);
638     gle::attrib(e.attached->o);
639     xtraverts += gle::end();
640 }
641 
renderentarrow(const extentity & e,const vec & dir,float radius)642 void renderentarrow(const extentity &e, const vec &dir, float radius)
643 {
644     if(radius <= 0) return;
645     float arrowsize = min(radius/8, 0.5f);
646     vec target = vec(dir).mul(radius).add(e.o), arrowbase = vec(dir).mul(radius - arrowsize).add(e.o), spoke;
647     spoke.orthogonal(dir);
648     spoke.normalize();
649     spoke.mul(arrowsize);
650 
651     gle::defvertex();
652 
653     gle::begin(GL_LINES);
654     gle::attrib(e.o);
655     gle::attrib(target);
656     xtraverts += gle::end();
657 
658     gle::begin(GL_TRIANGLE_FAN);
659     gle::attrib(target);
660     loopi(5) gle::attrib(vec(spoke).rotate(2*M_PI*i/4.0f, dir).add(arrowbase));
661     xtraverts += gle::end();
662 }
663 
renderentcone(const extentity & e,const vec & dir,float radius,float angle)664 void renderentcone(const extentity &e, const vec &dir, float radius, float angle)
665 {
666     if(radius <= 0) return;
667     vec spot = vec(dir).mul(radius*cosf(angle*RAD)).add(e.o), spoke;
668     spoke.orthogonal(dir);
669     spoke.normalize();
670     spoke.mul(radius*sinf(angle*RAD));
671 
672     gle::defvertex();
673 
674     gle::begin(GL_LINES);
675     loopi(8)
676     {
677         gle::attrib(e.o);
678         gle::attrib(vec(spoke).rotate(2*M_PI*i/8.0f, dir).add(spot));
679     }
680     xtraverts += gle::end();
681 
682     gle::begin(GL_LINE_LOOP);
683     loopi(8) gle::attrib(vec(spoke).rotate(2*M_PI*i/8.0f, dir).add(spot));
684     xtraverts += gle::end();
685 }
686 
renderentbox(const extentity & e,const vec & center,const vec & radius,int yaw,int pitch,int roll)687 void renderentbox(const extentity &e, const vec &center, const vec &radius, int yaw, int pitch, int roll)
688 {
689     matrix4x3 orient;
690     orient.identity();
691     orient.settranslation(e.o);
692     if(yaw) orient.rotate_around_z(sincosmod360(yaw));
693     if(pitch) orient.rotate_around_x(sincosmod360(pitch));
694     if(roll) orient.rotate_around_y(sincosmod360(-roll));
695     orient.translate(center);
696 
697     gle::defvertex();
698 
699     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) },
700         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) };
701     loopi(4)
702     {
703         front[i] = orient.transform(front[i]);
704         back[i] = orient.transform(back[i]);
705     }
706 
707     gle::begin(GL_LINE_LOOP);
708     loopi(4) gle::attrib(front[i]);
709     xtraverts += gle::end();
710 
711     gle::begin(GL_LINES);
712     gle::attrib(front[0]);
713         gle::attrib(front[2]);
714     gle::attrib(front[1]);
715         gle::attrib(front[3]);
716     loopi(4)
717     {
718         gle::attrib(front[i]);
719         gle::attrib(back[i]);
720     }
721     xtraverts += gle::end();
722 
723     gle::begin(GL_LINE_LOOP);
724     loopi(4) gle::attrib(back[i]);
725     xtraverts += gle::end();
726 }
727 
renderentradius(extentity & e,bool color)728 void renderentradius(extentity &e, bool color)
729 {
730     switch(e.type)
731     {
732         case ET_LIGHT:
733             if(e.attr1 <= 0) break;
734             if(color) gle::colorf(e.attr2/255.0f, e.attr3/255.0f, e.attr4/255.0f);
735             renderentsphere(e, e.attr1);
736             break;
737 
738         case ET_SPOTLIGHT:
739             if(e.attached)
740             {
741                 if(color) gle::colorf(0, 1, 1);
742                 float radius = e.attached->attr1;
743                 if(radius <= 0) break;
744                 vec dir = vec(e.o).sub(e.attached->o).normalize();
745                 float angle = clamp(int(e.attr1), 1, 89);
746                 renderentattachment(e);
747                 renderentcone(*e.attached, dir, radius, angle);
748             }
749             break;
750 
751         case ET_SOUND:
752             if(color) gle::colorf(0, 1, 1);
753             renderentsphere(e, e.attr2);
754             break;
755 
756         case ET_ENVMAP:
757         {
758             extern int envmapradius;
759             if(color) gle::colorf(0, 1, 1);
760             renderentsphere(e, e.attr1 ? max(0, min(10000, int(e.attr1))) : envmapradius);
761             break;
762         }
763 
764         case ET_MAPMODEL:
765         {
766             if(color) gle::colorf(0, 1, 1);
767             entities::entradius(e, color);
768             vec dir;
769             vecfromyawpitch(e.attr2, e.attr3, 1, 0, dir);
770             renderentarrow(e, dir, 4);
771             break;
772         }
773 
774         case ET_PLAYERSTART:
775         {
776             if(color) gle::colorf(0, 1, 1);
777             entities::entradius(e, color);
778             vec dir;
779             vecfromyawpitch(e.attr1, 0, 1, 0, dir);
780             renderentarrow(e, dir, 4);
781             break;
782         }
783 
784         case ET_DECAL:
785         {
786             if(color) gle::colorf(0, 1, 1);
787             DecalSlot &s = lookupdecalslot(e.attr1, false);
788             float size = max(float(e.attr5), 1.0f);
789             renderentbox(e, vec(0, s.depth * size/2, 0), vec(size/2, s.depth * size/2, size/2), e.attr2, e.attr3, e.attr4);
790             break;
791         }
792 
793         default:
794             if(e.type>=ET_GAMESPECIFIC)
795             {
796                 if(color) gle::colorf(0, 1, 1);
797                 entities::entradius(e, color);
798             }
799             break;
800     }
801 }
802 
renderentbox(const vec & eo,vec es)803 static void renderentbox(const vec &eo, vec es)
804 {
805     es.add(eo);
806 
807     // bottom quad
808     gle::attrib(eo.x, eo.y, eo.z); gle::attrib(es.x, eo.y, eo.z);
809     gle::attrib(es.x, eo.y, eo.z); gle::attrib(es.x, es.y, eo.z);
810     gle::attrib(es.x, es.y, eo.z); gle::attrib(eo.x, es.y, eo.z);
811     gle::attrib(eo.x, es.y, eo.z); gle::attrib(eo.x, eo.y, eo.z);
812 
813     // top quad
814     gle::attrib(eo.x, eo.y, es.z); gle::attrib(es.x, eo.y, es.z);
815     gle::attrib(es.x, eo.y, es.z); gle::attrib(es.x, es.y, es.z);
816     gle::attrib(es.x, es.y, es.z); gle::attrib(eo.x, es.y, es.z);
817     gle::attrib(eo.x, es.y, es.z); gle::attrib(eo.x, eo.y, es.z);
818 
819     // sides
820     gle::attrib(eo.x, eo.y, eo.z); gle::attrib(eo.x, eo.y, es.z);
821     gle::attrib(es.x, eo.y, eo.z); gle::attrib(es.x, eo.y, es.z);
822     gle::attrib(es.x, es.y, eo.z); gle::attrib(es.x, es.y, es.z);
823     gle::attrib(eo.x, es.y, eo.z); gle::attrib(eo.x, es.y, es.z);
824 }
825 
renderentselection(const vec & o,const vec & ray,bool entmoving)826 void renderentselection(const vec &o, const vec &ray, bool entmoving)
827 {
828     if(noentedit() || (entgroup.empty() && enthover < 0)) return;
829     vec eo, es;
830 
831     if(entgroup.length())
832     {
833         gle::colorub(0, 40, 0);
834         gle::defvertex();
835         gle::begin(GL_LINES, entgroup.length()*24);
836         loopv(entgroup) entfocus(entgroup[i],
837             entselectionbox(e, eo, es);
838             renderentbox(eo, es);
839         );
840         xtraverts += gle::end();
841     }
842 
843     if(enthover >= 0)
844     {
845         gle::colorub(0, 40, 0);
846         entfocus(enthover, entselectionbox(e, eo, es)); // also ensures enthover is back in focus
847         boxs3D(eo, es, 1);
848         if(entmoving && entmovingshadow==1)
849         {
850             vec a, b;
851             gle::colorub(20, 20, 20);
852             (a = eo).x = eo.x - fmod(eo.x, worldsize); (b = es).x = a.x + worldsize; boxs3D(a, b, 1);
853             (a = eo).y = eo.y - fmod(eo.y, worldsize); (b = es).y = a.x + worldsize; boxs3D(a, b, 1);
854             (a = eo).z = eo.z - fmod(eo.z, worldsize); (b = es).z = a.x + worldsize; boxs3D(a, b, 1);
855         }
856         gle::colorub(200,0,0);
857         boxs(entorient, eo, es);
858     }
859 
860     if(showentradius)
861     {
862         glDepthFunc(GL_GREATER);
863         gle::colorf(0.25f, 0.25f, 0.25f);
864         loopv(entgroup) entfocus(entgroup[i], renderentradius(e, false));
865         if(enthover>=0) entfocus(enthover, renderentradius(e, false));
866         glDepthFunc(GL_LESS);
867         loopv(entgroup) entfocus(entgroup[i], renderentradius(e, true));
868         if(enthover>=0) entfocus(enthover, renderentradius(e, true));
869     }
870 
871     gle::disable();
872 }
873 
enttoggle(int id)874 bool enttoggle(int id)
875 {
876     undonext = true;
877     int i = entgroup.find(id);
878     if(i < 0)
879         entadd(id);
880     else
881         entgroup.remove(i);
882     return i < 0;
883 }
884 
hoveringonent(int ent,int orient)885 bool hoveringonent(int ent, int orient)
886 {
887     if(noentedit()) return false;
888     entorient = orient;
889     if((efocus = enthover = ent) >= 0)
890         return true;
891     efocus   = entgroup.empty() ? -1 : entgroup.last();
892     enthover = -1;
893     return false;
894 }
895 
896 VAR(entitysurf, 0, 0, 1);
897 
898 ICOMMAND(entadd, "", (),
899 {
900     if(enthover >= 0 && !noentedit())
901     {
902         if(entgroup.find(enthover) < 0) entadd(enthover);
903         if(entmoving > 1) entmoving = 1;
904     }
905 });
906 
907 ICOMMAND(enttoggle, "", (),
908 {
909     if(enthover < 0 || noentedit() || !enttoggle(enthover)) { entmoving = 0; intret(0); }
910     else { if(entmoving > 1) entmoving = 1; intret(1); }
911 });
912 
913 ICOMMAND(entmoving, "b", (int *n),
914 {
915     if(*n >= 0)
916     {
917         if(!*n || enthover < 0 || noentedit()) entmoving = 0;
918         else
919         {
920             if(entgroup.find(enthover) < 0) { entadd(enthover); entmoving = 1; }
921             else if(!entmoving) entmoving = 1;
922         }
923     }
924     intret(entmoving);
925 });
926 
entpush(int * dir)927 void entpush(int *dir)
928 {
929     if(noentedit()) return;
930     int d = dimension(entorient);
931     int s = dimcoord(entorient) ? -*dir : *dir;
932     if(entmoving)
933     {
934         groupeditpure(e.o[d] += float(s*sel.grid)); // editdrag supplies the undo
935     }
936     else
937         groupedit(e.o[d] += float(s*sel.grid));
938     if(entitysurf==1)
939     {
940         player->o[d] += float(s*sel.grid);
941         player->resetinterp();
942     }
943 }
944 
945 VAR(entautoviewdist, 0, 25, 100);
entautoview(int * dir)946 void entautoview(int *dir)
947 {
948     if(!haveselent()) return;
949     static int s = 0;
950     vec v(player->o);
951     v.sub(worldpos);
952     v.normalize();
953     v.mul(entautoviewdist);
954     int t = s + *dir;
955     s = abs(t) % entgroup.length();
956     if(t<0 && s>0) s = entgroup.length() - s;
957     entfocus(entgroup[s],
958         v.add(e.o);
959         player->o = v;
960         player->resetinterp();
961     );
962 }
963 
964 COMMAND(entautoview, "i");
965 COMMAND(entflip, "");
966 COMMAND(entrotate, "i");
967 COMMAND(entpush, "i");
968 
delent()969 void delent()
970 {
971     if(noentedit()) return;
972     groupedit(e.type = ET_EMPTY;);
973     entcancel();
974 }
975 
findtype(char * what)976 int findtype(char *what)
977 {
978     for(int i = 0; *entities::entname(i); i++) if(strcmp(what, entities::entname(i))==0) return i;
979     conoutf(CON_ERROR, "unknown entity type \"%s\"", what);
980     return ET_EMPTY;
981 }
982 
983 VAR(entdrop, 0, 2, 3);
984 
dropentity(entity & e,int drop=-1)985 bool dropentity(entity &e, int drop = -1)
986 {
987     vec radius(4.0f, 4.0f, 4.0f);
988     if(drop<0) drop = entdrop;
989     if(e.type == ET_MAPMODEL)
990     {
991         model *m = loadmapmodel(e.attr1);
992         if(m)
993         {
994             vec center;
995             mmboundbox(e, m, center, radius);
996             radius.x += fabs(center.x);
997             radius.y += fabs(center.y);
998         }
999         radius.z = 0.0f;
1000     }
1001     switch(drop)
1002     {
1003     case 1:
1004         if(e.type != ET_LIGHT && e.type != ET_SPOTLIGHT)
1005             dropenttofloor(&e);
1006         break;
1007     case 2:
1008     case 3:
1009         int cx = 0, cy = 0;
1010         if(sel.cxs == 1 && sel.cys == 1)
1011         {
1012             cx = (sel.cx ? 1 : -1) * sel.grid / 2;
1013             cy = (sel.cy ? 1 : -1) * sel.grid / 2;
1014         }
1015         e.o = vec(sel.o);
1016         int d = dimension(sel.orient), dc = dimcoord(sel.orient);
1017         e.o[R[d]] += sel.grid / 2 + cx;
1018         e.o[C[d]] += sel.grid / 2 + cy;
1019         if(!dc)
1020             e.o[D[d]] -= radius[D[d]];
1021         else
1022             e.o[D[d]] += sel.grid + radius[D[d]];
1023 
1024         if(drop == 3)
1025             dropenttofloor(&e);
1026         break;
1027     }
1028     return true;
1029 }
1030 
dropent()1031 void dropent()
1032 {
1033     if(noentedit()) return;
1034     groupedit(dropentity(e));
1035 }
1036 
attachent()1037 void attachent()
1038 {
1039     if(noentedit()) return;
1040     groupedit(attachentity(e));
1041 }
1042 
1043 COMMAND(attachent, "");
1044 
1045 static int keepents = 0;
1046 
newentity(bool local,const vec & o,int type,int v1,int v2,int v3,int v4,int v5,int & idx,bool fix=true)1047 extentity *newentity(bool local, const vec &o, int type, int v1, int v2, int v3, int v4, int v5, int &idx, bool fix = true)
1048 {
1049     vector<extentity *> &ents = entities::getents();
1050     if(local)
1051     {
1052         idx = -1;
1053         for(int i = keepents; i < ents.length(); i++) if(ents[i]->type == ET_EMPTY) { idx = i; break; }
1054         if(idx < 0 && ents.length() >= MAXENTS) { conoutf("too many entities"); return NULL; }
1055     }
1056     else while(ents.length() < idx) ents.add(entities::newentity())->type = ET_EMPTY;
1057     extentity &e = *entities::newentity();
1058     e.o = o;
1059     e.attr1 = v1;
1060     e.attr2 = v2;
1061     e.attr3 = v3;
1062     e.attr4 = v4;
1063     e.attr5 = v5;
1064     e.type = type;
1065     e.reserved = 0;
1066     if(local && fix)
1067     {
1068         switch(type)
1069         {
1070                 case ET_DECAL:
1071                     if(!e.attr2 && !e.attr3 && !e.attr4)
1072                     {
1073                         e.attr2 = (int)camera1->yaw;
1074                         e.attr3 = (int)camera1->pitch;
1075                         e.attr4 = (int)camera1->roll;
1076                     }
1077                     break;
1078                 case ET_MAPMODEL:
1079                     if(!e.attr2) e.attr2 = (int)camera1->yaw;
1080                     break;
1081                 case ET_PLAYERSTART:
1082                     e.attr5 = e.attr4;
1083                     e.attr4 = e.attr3;
1084                     e.attr3 = e.attr2;
1085                     e.attr2 = e.attr1;
1086                     e.attr1 = (int)camera1->yaw;
1087                     break;
1088         }
1089         entities::fixentity(e);
1090     }
1091     if(ents.inrange(idx)) { entities::deleteentity(ents[idx]); ents[idx] = &e; }
1092     else { idx = ents.length(); ents.add(&e); }
1093     return &e;
1094 }
1095 
newentity(int type,int a1,int a2,int a3,int a4,int a5,bool fix=true)1096 void newentity(int type, int a1, int a2, int a3, int a4, int a5, bool fix = true)
1097 {
1098     int idx;
1099     extentity *t = newentity(true, player->o, type, a1, a2, a3, a4, a5, idx, fix);
1100     if(!t) return;
1101     dropentity(*t);
1102     t->type = ET_EMPTY;
1103     enttoggle(idx);
1104     makeundoent();
1105     entedit(idx, e.type = type);
1106     commitchanges();
1107 }
1108 
newent(char * what,int * a1,int * a2,int * a3,int * a4,int * a5)1109 void newent(char *what, int *a1, int *a2, int *a3, int *a4, int *a5)
1110 {
1111     if(noentedit()) return;
1112     int type = findtype(what);
1113     if(type != ET_EMPTY)
1114         newentity(type, *a1, *a2, *a3, *a4, *a5);
1115 }
1116 
1117 int entcopygrid;
1118 vector<entity> entcopybuf;
1119 
entcopy()1120 void entcopy()
1121 {
1122     if(noentedit()) return;
1123     entcopygrid = sel.grid;
1124     entcopybuf.shrink(0);
1125     addimplicit({
1126         loopv(entgroup) entfocus(entgroup[i], entcopybuf.add(e).o.sub(vec(sel.o)));
1127     });
1128 }
1129 
entpaste()1130 void entpaste()
1131 {
1132     if(noentedit() || entcopybuf.empty()) return;
1133     entcancel();
1134     float m = float(sel.grid)/float(entcopygrid);
1135     loopv(entcopybuf)
1136     {
1137         const entity &c = entcopybuf[i];
1138         vec o = vec(c.o).mul(m).add(vec(sel.o));
1139         int idx;
1140         extentity *e = newentity(true, o, ET_EMPTY, c.attr1, c.attr2, c.attr3, c.attr4, c.attr5, idx);
1141         if(!e) continue;
1142         entadd(idx);
1143         keepents = max(keepents, idx+1);
1144     }
1145     keepents = 0;
1146     int j = 0;
1147     groupeditundo(e.type = entcopybuf[j++].type;);
1148 }
1149 
entreplace()1150 void entreplace()
1151 {
1152     if(noentedit() || entcopybuf.empty()) return;
1153     const entity &c = entcopybuf[0];
1154     if(entgroup.length() || enthover >= 0)
1155     {
1156         groupedit({
1157             e.type = c.type;
1158             e.attr1 = c.attr1;
1159             e.attr2 = c.attr2;
1160             e.attr3 = c.attr3;
1161             e.attr4 = c.attr4;
1162             e.attr5 = c.attr5;
1163         });
1164     }
1165     else
1166     {
1167         newentity(c.type, c.attr1, c.attr2, c.attr3, c.attr4, c.attr5, false);
1168     }
1169 }
1170 
1171 COMMAND(newent, "siiiii");
1172 COMMAND(delent, "");
1173 COMMAND(dropent, "");
1174 COMMAND(entcopy, "");
1175 COMMAND(entpaste, "");
1176 COMMAND(entreplace, "");
1177 
entset(char * what,int * a1,int * a2,int * a3,int * a4,int * a5)1178 void entset(char *what, int *a1, int *a2, int *a3, int *a4, int *a5)
1179 {
1180     if(noentedit()) return;
1181     int type = findtype(what);
1182     if(type != ET_EMPTY)
1183         groupedit(e.type=type;
1184                   e.attr1=*a1;
1185                   e.attr2=*a2;
1186                   e.attr3=*a3;
1187                   e.attr4=*a4;
1188                   e.attr5=*a5);
1189 }
1190 
printent(extentity & e,char * buf,int len)1191 void printent(extentity &e, char *buf, int len)
1192 {
1193     switch(e.type)
1194     {
1195         case ET_PARTICLES:
1196             if(printparticles(e, buf, len)) return;
1197             break;
1198 
1199         default:
1200             if(e.type >= ET_GAMESPECIFIC && entities::printent(e, buf, len)) return;
1201             break;
1202     }
1203     nformatstring(buf, len, "%s %d %d %d %d %d", entities::entname(e.type), e.attr1, e.attr2, e.attr3, e.attr4, e.attr5);
1204 }
1205 
nearestent()1206 void nearestent()
1207 {
1208     if(noentedit()) return;
1209     int closest = -1;
1210     float closedist = 1e16f;
1211     vector<extentity *> &ents = entities::getents();
1212     loopv(ents)
1213     {
1214         extentity &e = *ents[i];
1215         if(e.type == ET_EMPTY) continue;
1216         float dist = e.o.dist(player->o);
1217         if(dist < closedist)
1218         {
1219             closest = i;
1220             closedist = dist;
1221         }
1222     }
1223     if(closest >= 0) entadd(closest);
1224 }
1225 
1226 ICOMMAND(enthavesel,"",  (), addimplicit(intret(entgroup.length())));
1227 ICOMMAND(entselect, "e", (uint *body), if(!noentedit()) addgroup(e.type != ET_EMPTY && entgroup.find(n)<0 && executebool(body)));
1228 ICOMMAND(entloop,   "e", (uint *body), if(!noentedit()) addimplicit(groupeditloop(((void)e, execute(body)))));
1229 ICOMMAND(insel,     "",  (), entfocus(efocus, intret(pointinsel(sel, e.o))));
1230 ICOMMAND(entget,    "",  (), entfocus(efocus, string s; printent(e, s, sizeof(s)); result(s)));
1231 ICOMMAND(entindex,  "",  (), intret(efocus));
1232 COMMAND(entset, "siiiii");
1233 COMMAND(nearestent, "");
1234 
enttype(char * type,int * numargs)1235 void enttype(char *type, int *numargs)
1236 {
1237     if(*numargs >= 1)
1238     {
1239         int typeidx = findtype(type);
1240         if(typeidx != ET_EMPTY) groupedit(e.type = typeidx);
1241     }
1242     else entfocus(efocus,
1243     {
1244         result(entities::entname(e.type));
1245     })
1246 }
1247 
entattr(int * attr,int * val,int * numargs)1248 void entattr(int *attr, int *val, int *numargs)
1249 {
1250     if(*numargs >= 2)
1251     {
1252         if(*attr >= 0 && *attr <= 4)
1253             groupedit(
1254                 switch(*attr)
1255                 {
1256                     case 0: e.attr1 = *val; break;
1257                     case 1: e.attr2 = *val; break;
1258                     case 2: e.attr3 = *val; break;
1259                     case 3: e.attr4 = *val; break;
1260                     case 4: e.attr5 = *val; break;
1261                 }
1262             );
1263     }
1264     else entfocus(efocus,
1265     {
1266         switch(*attr)
1267         {
1268             case 0: intret(e.attr1); break;
1269             case 1: intret(e.attr2); break;
1270             case 2: intret(e.attr3); break;
1271             case 3: intret(e.attr4); break;
1272             case 4: intret(e.attr5); break;
1273         }
1274     });
1275 }
1276 
1277 COMMAND(enttype, "sN");
1278 COMMAND(entattr, "iiN");
1279 
findentity(int type,int index,int attr1,int attr2)1280 int findentity(int type, int index, int attr1, int attr2)
1281 {
1282     const vector<extentity *> &ents = entities::getents();
1283     if(index > ents.length()) index = ents.length();
1284     else for(int i = index; i<ents.length(); i++)
1285     {
1286         extentity &e = *ents[i];
1287         if(e.type==type && (attr1<0 || e.attr1==attr1) && (attr2<0 || e.attr2==attr2))
1288             return i;
1289     }
1290     loopj(index)
1291     {
1292         extentity &e = *ents[j];
1293         if(e.type==type && (attr1<0 || e.attr1==attr1) && (attr2<0 || e.attr2==attr2))
1294             return j;
1295     }
1296     return -1;
1297 }
1298 
1299 int spawncycle = -1;
1300 
findplayerspawn(dynent * d,int forceent,int tag)1301 void findplayerspawn(dynent *d, int forceent, int tag) // place at random spawn
1302 {
1303     int pick = forceent;
1304     if(pick<0)
1305     {
1306         int r = rnd(10)+1;
1307         pick = spawncycle;
1308         loopi(r)
1309         {
1310             pick = findentity(ET_PLAYERSTART, pick+1, -1, tag);
1311             if(pick < 0) break;
1312         }
1313         if(pick < 0 && tag)
1314         {
1315             pick = spawncycle;
1316             loopi(r)
1317             {
1318                 pick = findentity(ET_PLAYERSTART, pick+1, -1, 0);
1319                 if(pick < 0) break;
1320             }
1321         }
1322         if(pick >= 0) spawncycle = pick;
1323     }
1324     if(pick>=0)
1325     {
1326         const vector<extentity *> &ents = entities::getents();
1327         d->pitch = 0;
1328         d->roll = 0;
1329         for(int attempt = pick;;)
1330         {
1331             d->o = ents[attempt]->o;
1332             d->yaw = ents[attempt]->attr1;
1333             if(entinmap(d, true)) break;
1334             attempt = findentity(ET_PLAYERSTART, attempt+1, -1, tag);
1335             if(attempt<0 || attempt==pick)
1336             {
1337                 d->o = ents[pick]->o;
1338                 d->yaw = ents[pick]->attr1;
1339                 entinmap(d);
1340                 break;
1341             }
1342         }
1343     }
1344     else
1345     {
1346         d->o.x = d->o.y = d->o.z = 0.5f*worldsize;
1347         d->o.z += 1;
1348         entinmap(d);
1349     }
1350     if(d == player) ovr::reset();
1351 }
1352 
splitocta(cube * c,int size)1353 void splitocta(cube *c, int size)
1354 {
1355     if(size <= 0x1000) return;
1356     loopi(8)
1357     {
1358         if(!c[i].children) c[i].children = newcubes(isempty(c[i]) ? F_EMPTY : F_SOLID);
1359         splitocta(c[i].children, size>>1);
1360     }
1361 }
1362 
resetmap()1363 void resetmap()
1364 {
1365     clearoverrides();
1366     clearmapsounds();
1367     resetblendmap();
1368     clearlights();
1369     clearpvs();
1370     clearslots();
1371     clearparticles();
1372     clearstains();
1373     clearsleep();
1374     cancelsel();
1375     pruneundos();
1376     clearmapcrc();
1377 
1378     entities::clearents();
1379     outsideents.setsize(0);
1380     spotlights = 0;
1381 }
1382 
startmap(const char * name)1383 void startmap(const char *name)
1384 {
1385     game::startmap(name);
1386     ovr::reset();
1387 }
1388 
emptymap(int scale,bool force,const char * mname,bool usecfg)1389 bool emptymap(int scale, bool force, const char *mname, bool usecfg)    // main empty world creation routine
1390 {
1391     if(!force && !editmode)
1392     {
1393         conoutf(CON_ERROR, "newmap only allowed in edit mode");
1394         return false;
1395     }
1396 
1397     resetmap();
1398 
1399     setvar("mapscale", scale<10 ? 10 : (scale>16 ? 16 : scale), true, false);
1400     setvar("mapsize", 1<<worldscale, true, false);
1401     setvar("emptymap", 1, true, false);
1402 
1403     texmru.shrink(0);
1404     freeocta(worldroot);
1405     worldroot = newcubes(F_EMPTY);
1406     loopi(4) solidfaces(worldroot[i]);
1407 
1408     if(worldsize > 0x1000) splitocta(worldroot, worldsize>>1);
1409 
1410     clearmainmenu();
1411 
1412     if(usecfg)
1413     {
1414         identflags |= IDF_OVERRIDDEN;
1415         execfile("config/default_map_settings.cfg", false);
1416         identflags &= ~IDF_OVERRIDDEN;
1417     }
1418 
1419     initlights();
1420     allchanged(usecfg);
1421 
1422     startmap(mname);
1423 
1424     return true;
1425 }
1426 
enlargemap(bool force)1427 bool enlargemap(bool force)
1428 {
1429     if(!force && !editmode)
1430     {
1431         conoutf(CON_ERROR, "mapenlarge only allowed in edit mode");
1432         return false;
1433     }
1434     if(worldsize >= 1<<16) return false;
1435 
1436     while(outsideents.length()) removeentity(outsideents.pop());
1437 
1438     worldscale++;
1439     worldsize *= 2;
1440     cube *c = newcubes(F_EMPTY);
1441     c[0].children = worldroot;
1442     loopi(3) solidfaces(c[i+1]);
1443     worldroot = c;
1444 
1445     if(worldsize > 0x1000) splitocta(worldroot, worldsize>>1);
1446 
1447     enlargeblendmap();
1448 
1449     allchanged();
1450 
1451     return true;
1452 }
1453 
isallempty(cube & c)1454 static bool isallempty(cube &c)
1455 {
1456     if(!c.children) return isempty(c);
1457     loopi(8) if(!isallempty(c.children[i])) return false;
1458     return true;
1459 }
1460 
shrinkmap()1461 void shrinkmap()
1462 {
1463     extern int nompedit;
1464     if(noedit(true) || (nompedit && multiplayer())) return;
1465     if(worldsize <= 1<<10) return;
1466 
1467     int octant = -1;
1468     loopi(8) if(!isallempty(worldroot[i]))
1469     {
1470         if(octant >= 0) return;
1471         octant = i;
1472     }
1473     if(octant < 0) return;
1474 
1475     while(outsideents.length()) removeentity(outsideents.pop());
1476 
1477     if(!worldroot[octant].children) subdividecube(worldroot[octant], false, false);
1478     cube *root = worldroot[octant].children;
1479     worldroot[octant].children = NULL;
1480     freeocta(worldroot);
1481     worldroot = root;
1482     worldscale--;
1483     worldsize /= 2;
1484 
1485     ivec offset(octant, ivec(0, 0, 0), worldsize);
1486     vector<extentity *> &ents = entities::getents();
1487     loopv(ents) ents[i]->o.sub(vec(offset));
1488 
1489     shrinkblendmap(octant);
1490 
1491     allchanged();
1492 
1493     conoutf("shrunk map to size %d", worldscale);
1494 }
1495 
newmap(int * i)1496 void newmap(int *i) { bool force = !isconnected(); if(force) game::forceedit(""); if(emptymap(*i, force, NULL)) game::newmap(max(*i, 0)); }
mapenlarge()1497 void mapenlarge() { if(enlargemap(false)) game::newmap(-1); }
1498 COMMAND(newmap, "i");
1499 COMMAND(mapenlarge, "");
1500 COMMAND(shrinkmap, "");
1501 
mapname()1502 void mapname()
1503 {
1504     result(game::getclientmap());
1505 }
1506 
1507 COMMAND(mapname, "");
1508 
mpeditent(int i,const vec & o,int type,int attr1,int attr2,int attr3,int attr4,int attr5,bool local)1509 void mpeditent(int i, const vec &o, int type, int attr1, int attr2, int attr3, int attr4, int attr5, bool local)
1510 {
1511     if(i < 0 || i >= MAXENTS) return;
1512     vector<extentity *> &ents = entities::getents();
1513     if(ents.length()<=i)
1514     {
1515         extentity *e = newentity(local, o, type, attr1, attr2, attr3, attr4, attr5, i);
1516         if(!e) return;
1517         addentityedit(i);
1518         attachentity(*e);
1519     }
1520     else
1521     {
1522         extentity &e = *ents[i];
1523         removeentityedit(i);
1524         int oldtype = e.type;
1525         if(oldtype!=type) detachentity(e);
1526         e.type = type;
1527         e.o = o;
1528         e.attr1 = attr1; e.attr2 = attr2; e.attr3 = attr3; e.attr4 = attr4; e.attr5 = attr5;
1529         addentityedit(i);
1530         if(oldtype!=type) attachentity(e);
1531     }
1532     entities::editent(i, local);
1533     clearshadowcache();
1534     commitchanges();
1535 }
1536 
getworldsize()1537 int getworldsize() { return worldsize; }
getmapversion()1538 int getmapversion() { return mapversion; }
1539 
1540