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 ¢er, 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 ¢er, 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 ¢er, 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 ¢er, 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 ¢er, 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