Initial sauer
[SauerbratenRemote.git] / src / engine / world.cpp
blob142870824100ebcc81343726a0f43722a57a8008
1 // world.cpp: core map management stuff
3 #include "pch.h"
4 #include "engine.h"
6 header hdr;
8 VAR(octaentsize, 0, 128, 1024);
9 VAR(entselradius, 0, 2, 10);
11 bool getentboundingbox(extentity &e, ivec &o, ivec &r)
13 switch(e.type)
15 case ET_EMPTY:
16 return false;
17 case ET_MAPMODEL:
19 model *m = loadmodel(NULL, e.attr2);
20 if(m)
22 vec center, radius;
23 m->boundbox(0, center, radius);
24 rotatebb(center, radius, e.attr1);
26 o = e.o;
27 o.add(center);
28 r = radius;
29 r.add(1);
30 o.sub(r);
31 r.mul(2);
32 break;
35 // invisible mapmodels use entselradius
36 default:
37 o = e.o;
38 o.sub(entselradius);
39 r.x = r.y = r.z = entselradius*2;
40 break;
42 return true;
45 void modifyoctaentity(bool add, int id, cube *c, const ivec &cor, int size, const ivec &bo, const ivec &br, vtxarray *lastva = NULL)
47 loopoctabox(cor, size, bo, br)
49 ivec o(i, cor.x, cor.y, cor.z, size);
50 vtxarray *va = c[i].ext && c[i].ext->va ? c[i].ext->va : lastva;
51 if(c[i].children != NULL && size > octaentsize)
52 modifyoctaentity(add, id, c[i].children, o, size>>1, bo, br, va);
53 else if(add)
55 if(!c[i].ext || !c[i].ext->ents) ext(c[i]).ents = new octaentities(o, size);
56 octaentities &oe = *c[i].ext->ents;
57 switch(et->getents()[id]->type)
59 case ET_MAPMODEL:
60 if(loadmodel(NULL, et->getents()[id]->attr2))
62 if(va && oe.mapmodels.empty())
64 if(!va->mapmodels) va->mapmodels = new vector<octaentities *>;
65 va->mapmodels->add(&oe);
67 oe.mapmodels.add(id);
68 loopk(3)
70 oe.bbmin[k] = min(oe.bbmin[k], max(oe.o[k], bo[k]));
71 oe.bbmax[k] = max(oe.bbmax[k], min(oe.o[k]+size, bo[k]+br[k]));
73 break;
75 // invisible mapmodel
76 default:
77 oe.other.add(id);
78 break;
82 else if(c[i].ext && c[i].ext->ents)
84 octaentities &oe = *c[i].ext->ents;
85 switch(et->getents()[id]->type)
87 case ET_MAPMODEL:
88 if(loadmodel(NULL, et->getents()[id]->attr2))
90 oe.mapmodels.removeobj(id);
91 if(va && va->mapmodels && oe.mapmodels.empty())
93 va->mapmodels->removeobj(&oe);
94 if(va->mapmodels->empty()) DELETEP(va->mapmodels);
96 oe.bbmin = oe.bbmax = oe.o;
97 oe.bbmin.add(oe.size);
98 loopvj(oe.mapmodels)
100 extentity &e = *et->getents()[oe.mapmodels[j]];
101 ivec eo, er;
102 if(getentboundingbox(e, eo, er)) loopk(3)
104 oe.bbmin[k] = min(oe.bbmin[k], eo[k]);
105 oe.bbmax[k] = max(oe.bbmax[k], eo[k]+er[k]);
108 loopk(3)
110 oe.bbmin[k] = max(oe.bbmin[k], oe.o[k]);
111 oe.bbmax[k] = min(oe.bbmax[k], oe.o[k]+size);
113 break;
115 // invisible mapmodel
116 default:
117 oe.other.removeobj(id);
118 break;
120 if(oe.mapmodels.empty() && oe.other.empty())
121 freeoctaentities(c[i]);
123 if(c[i].ext && c[i].ext->ents) c[i].ext->ents->query = NULL;
127 static void modifyoctaent(bool add, int id)
129 vector<extentity *> &ents = et->getents();
130 if(!ents.inrange(id)) return;
131 ivec o, r;
132 extentity &e = *ents[id];
133 if((e.inoctanode!=0)==add || !getentboundingbox(e, o, r)) return;
134 e.inoctanode = add;
135 modifyoctaentity(add, id, worldroot, ivec(0, 0, 0), hdr.worldsize>>1, o, r);
136 if(e.type == ET_LIGHT) clearlightcache(id);
137 else if(add) lightent(e);
140 static inline void addentity(int id) { modifyoctaent(true, id); }
141 static inline void removeentity(int id) { modifyoctaent(false, id); }
143 void freeoctaentities(cube &c)
145 if(!c.ext) return;
146 if(et->getents().length())
148 while(c.ext->ents && !c.ext->ents->mapmodels.empty()) removeentity(c.ext->ents->mapmodels.pop());
149 while(c.ext->ents && !c.ext->ents->other.empty()) removeentity(c.ext->ents->other.pop());
151 if(c.ext->ents)
153 delete c.ext->ents;
154 c.ext->ents = NULL;
158 void entitiesinoctanodes()
160 loopv(et->getents()) addentity(i);
163 char *entname(entity &e)
165 static string fullentname;
166 s_strcpy(fullentname, "@");
167 s_strcat(fullentname, et->entname(e.type));
168 const char *einfo = et->entnameinfo(e);
169 if(*einfo)
171 s_strcat(fullentname, ": ");
172 s_strcat(fullentname, einfo);
174 return fullentname;
177 extern selinfo sel;
178 extern bool havesel, selectcorners;
179 int entlooplevel = 0;
180 int efocus = -1, enthover = -1, entorient = -1, oldhover = -1;
181 bool undonext = true;
183 VAR(entediting, 0, 0, 1);
185 bool noentedit()
187 if(!editmode) { conoutf("operation only allowed in edit mode"); return true; }
188 return !entediting;
191 bool pointinsel(selinfo &sel, vec &o)
193 return(o.x <= sel.o.x+sel.s.x*sel.grid
194 && o.x >= sel.o.x
195 && o.y <= sel.o.y+sel.s.y*sel.grid
196 && o.y >= sel.o.y
197 && o.z <= sel.o.z+sel.s.z*sel.grid
198 && o.z >= sel.o.z);
201 vector<int> entgroup;
203 bool haveselent()
205 return entgroup.length() > 0;
208 void entcancel()
210 entgroup.setsize(0);
213 void entadd(int id)
215 undonext = true;
216 entgroup.add(id);
219 void initundoent(undoblock &u)
221 u.n = 0; u.e = NULL;
222 u.n = entgroup.length();
223 if(u.n<=0) return;
224 u.e = new undoent[u.n];
225 loopv(entgroup)
227 u.e->i = entgroup[i];
228 u.e->e = *et->getents()[entgroup[i]];
229 u.e++;
231 u.e -= u.n;
234 void makeundoent()
236 if(!undonext) return;
237 undonext = false;
238 oldhover = enthover;
239 undoblock u;
240 initundoent(u);
241 if(u.n) addundo(u);
244 void detachentity(extentity &e)
246 if(!e.attached) return;
247 e.attached->attached = NULL;
248 e.attached = NULL;
251 VAR(attachradius, 1, 100, 1000);
253 void attachentity(extentity &e)
255 switch(e.type)
257 case ET_SPOTLIGHT:
258 break;
260 default:
261 if(e.type<ET_GAMESPECIFIC || !et->mayattach(e)) return;
262 break;
265 detachentity(e);
267 vector<extentity *> &ents = et->getents();
268 int closest = -1;
269 float closedist = 1e10f;
270 loopv(ents)
272 extentity *a = ents[i];
273 if(a->attached) continue;
274 switch(e.type)
276 case ET_SPOTLIGHT:
277 if(a->type!=ET_LIGHT) continue;
278 break;
280 default:
281 if(e.type<ET_GAMESPECIFIC || !et->attachent(e, *a)) continue;
282 break;
284 float dist = e.o.dist(a->o);
285 if(dist < closedist)
287 closest = i;
288 closedist = dist;
291 if(closedist>attachradius) return;
292 e.attached = ents[closest];
293 ents[closest]->attached = &e;
296 void attachentities()
298 vector<extentity *> &ents = et->getents();
299 loopv(ents) attachentity(*ents[i]);
302 // convenience macros implicitly define:
303 // e entity, currently edited ent
304 // n int, index to currently edited ent
305 #define addimplicit(f) { if(entgroup.empty() && enthover>=0) { entadd(enthover); undonext = (enthover != oldhover); f; entgroup.drop(); } else f; }
306 #define entfocus(i, f) { int n = efocus = (i); if(n>=0) { extentity &e = *et->getents()[n]; f; } }
307 #define entedit(i, f) \
309 entfocus(i, \
310 int oldtype = e.type; \
311 removeentity(n); \
312 f; \
313 if(oldtype!=e.type) detachentity(e); \
314 if(e.type!=ET_EMPTY) { addentity(n); if(oldtype!=e.type) attachentity(e); } \
315 et->editent(n)); \
317 #define addgroup(exp) { loopv(et->getents()) entfocus(i, if(exp) entadd(n)); }
318 #define setgroup(exp) { entcancel(); addgroup(exp); }
319 #define groupeditloop(f){ entlooplevel++; int _ = efocus; loopv(entgroup) entedit(entgroup[i], f); efocus = _; entlooplevel--; }
320 #define groupeditpure(f){ if(entlooplevel>0) { entedit(efocus, f); } else groupeditloop(f); }
321 #define groupeditundo(f){ makeundoent(); groupeditpure(f); }
322 #define groupedit(f) { addimplicit(groupeditundo(f)); }
324 void copyundoents(undoblock &d, undoblock &s)
326 entcancel();
327 loopi(s.n)
328 entadd(s.e[i].i);
329 initundoent(d);
330 loopi(s.n) if(s.e[i].e.type==ET_EMPTY)
331 entgroup.remove(s.e[i].i);
334 void pasteundoents(undoblock &u)
336 loopi(u.n)
337 entedit(u.e[i].i, (entity &)e = u.e[i].e);
340 void entflip()
342 if(noentedit()) return;
343 int d = dimension(sel.orient);
344 float mid = sel.s[d]*sel.grid/2+sel.o[d];
345 groupeditundo(e.o[d] -= (e.o[d]-mid)*2);
348 void entrotate(int *cw)
350 if(noentedit()) return;
351 int d = dimension(sel.orient);
352 int dd = *cw<0 == dimcoord(sel.orient) ? R[d] : C[d];
353 float mid = sel.s[dd]*sel.grid/2+sel.o[dd];
354 vec s(sel.o.v);
355 groupeditundo(
356 e.o[dd] -= (e.o[dd]-mid)*2;
357 e.o.sub(s);
358 swap(float, e.o[R[d]], e.o[C[d]]);
359 e.o.add(s);
363 void entselectionbox(const entity &e, vec &eo, vec &es)
365 model *m = NULL;
366 if(e.type == ET_MAPMODEL && (m = loadmodel(NULL, e.attr2)))
368 m->collisionbox(0, eo, es);
369 rotatebb(eo, es, e.attr1);
370 if(m->collide)
371 eo.z -= player->aboveeye; // wacky but true. see physics collide
372 else
373 es.div(2); // cause the usual bb is too big...
374 eo.add(e.o);
376 else
378 es = vec(entselradius);
379 eo = e.o;
381 eo.sub(es);
382 es.mul(2);
385 VAR(entselsnap, 0, 0, 1);
386 VAR(entmovingshadow, 0, 1, 1);
388 extern void boxs(int orient, vec o, const vec &s);
389 extern void boxs3D(const vec &o, vec s, int g);
390 extern void editmoveplane(const vec &o, const vec &ray, int d, float off, vec &handle, vec &dest, bool first);
392 bool initentdragging = true;
394 void entdrag(const vec &ray)
396 if(noentedit() || !haveselent()) return;
398 float r = 0, c = 0;
399 static vec v, handle;
400 vec eo, es;
401 int d = dimension(entorient),
402 dc= dimcoord(entorient);
404 entfocus(entgroup.last(),
405 entselectionbox(e, eo, es);
407 editmoveplane(e.o, ray, d, eo[d] + (dc ? es[d] : 0), handle, v, initentdragging);
409 ivec g(v);
410 int z = g[d]&(~(sel.grid-1));
411 g.add(sel.grid/2).mask(~(sel.grid-1));
412 g[d] = z;
414 r = (entselsnap ? g[R[d]] : v[R[d]]) - e.o[R[d]];
415 c = (entselsnap ? g[C[d]] : v[C[d]]) - e.o[C[d]];
418 if(initentdragging) makeundoent();
419 groupeditpure(e.o[R[d]] += r; e.o[C[d]] += c);
420 initentdragging = false;
423 VAR(showentradius, 0, 1, 1);
425 void renderentradius(extentity &e)
427 if(!showentradius) return;
428 float radius = 0.0f, angle = 0.0f, ring = 0.0f;
429 vec dir(0, 0, 0);
430 float color[3] = {0, 1, 1};
431 switch(e.type)
433 case ET_LIGHT:
434 radius = e.attr1;
435 color[0] = e.attr2/255.0f;
436 color[1] = e.attr3/255.0f;
437 color[2] = e.attr4/255.0f;
438 break;
440 case ET_SPOTLIGHT:
441 if(e.attached)
443 radius = e.attached->attr1;
444 if(!radius) radius = 2*e.o.dist(e.attached->o);
445 dir = vec(e.o).sub(e.attached->o).normalize();
446 angle = max(1, min(90, e.attr1));
448 break;
450 case ET_SOUND:
451 radius = e.attr2;
452 break;
454 case ET_ENVMAP:
456 extern int envmapradius;
457 radius = e.attr1 ? max(0, min(10000, e.attr1)) : envmapradius;
458 break;
461 case ET_MAPMODEL:
462 case ET_PLAYERSTART:
463 radius = 4;
464 if(e.type==ET_MAPMODEL && e.attr3) ring = checktriggertype(e.attr3, TRIG_COLLIDE) ? 20 : 12;
465 vecfromyawpitch(e.attr1, 0, 1, 0, dir);
466 break;
468 default:
469 if(e.type>=ET_GAMESPECIFIC) et->entradius(e, radius, angle, dir);
470 break;
472 if(radius<=0) return;
473 glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
474 loopj(2)
476 if(!j)
478 glDepthFunc(GL_GREATER);
479 glColor3f(0.25f, 0.25f, 0.25f);
481 else
483 glDepthFunc(GL_LESS);
484 glColor3fv(color);
486 if(e.attached)
488 glBegin(GL_LINES);
489 glVertex3fv(e.o.v);
490 glVertex3fv(e.attached->o.v);
491 glEnd();
493 if(ring)
495 glBegin(GL_LINE_LOOP);
496 loopi(16)
498 vec p(e.o);
499 p.x += ring*cosf(2*M_PI*i/16.0f);
500 p.y += ring*sinf(2*M_PI*i/16.0f);
501 glVertex3fv(p.v);
503 glEnd();
505 if(dir.iszero()) loopk(3)
507 glBegin(GL_LINE_LOOP);
508 loopi(16)
510 vec p(e.o);
511 p[k>=2 ? 1 : 0] += radius*cosf(2*M_PI*i/16.0f);
512 p[k>=1 ? 2 : 1] += radius*sinf(2*M_PI*i/16.0f);
513 glVertex3fv(p.v);
515 glEnd();
517 else if(!angle)
519 float arrowsize = min(radius/8, 0.5f);
520 vec target(vec(dir).mul(radius).add(e.o)), arrowbase(vec(dir).mul(radius - arrowsize).add(e.o)), spoke;
521 spoke.orthogonal(dir);
522 spoke.normalize();
523 spoke.mul(arrowsize);
524 glBegin(GL_LINES);
525 glVertex3fv(e.o.v);
526 glVertex3fv(target.v);
527 glEnd();
528 glBegin(GL_TRIANGLE_FAN);
529 glVertex3fv(target.v);
530 loopi(5)
532 vec p(spoke);
533 p.rotate(2*M_PI*i/4.0f, dir);
534 p.add(arrowbase);
535 glVertex3fv(p.v);
537 glEnd();
539 else
541 vec spot(vec(dir).mul(radius*cosf(angle*RAD)).add(e.attached->o)), spoke;
542 spoke.orthogonal(dir);
543 spoke.normalize();
544 spoke.mul(radius*sinf(angle*RAD));
545 glBegin(GL_LINES);
546 loopi(8)
548 vec p(spoke);
549 p.rotate(2*M_PI*i/8.0f, dir);
550 p.add(spot);
551 glVertex3fv(e.attached->o.v);
552 glVertex3fv(p.v);
554 glEnd();
555 glBegin(GL_LINE_LOOP);
556 loopi(8)
558 vec p(spoke);
559 p.rotate(2*M_PI*i/8.0f, dir);
560 p.add(spot);
561 glVertex3fv(p.v);
563 glEnd();
566 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
569 void renderentselection(const vec &o, const vec &ray, bool entmoving)
571 if(noentedit()) return;
572 vec eo, es;
574 glColor3ub(0, 40, 0);
575 loopv(entgroup) entfocus(entgroup[i],
576 entselectionbox(e, eo, es);
577 boxs3D(eo, es, 1);
580 if(enthover >= 0)
582 entfocus(enthover, entselectionbox(e, eo, es)); // also ensures enthover is back in focus
583 boxs3D(eo, es, 1);
584 if(entmoving && entmovingshadow==1)
586 vec a, b;
587 glColor3ub(20, 20, 20);
588 (a=eo).x=0; (b=es).x=hdr.worldsize; boxs3D(a, b, 1);
589 (a=eo).y=0; (b=es).y=hdr.worldsize; boxs3D(a, b, 1);
590 (a=eo).z=0; (b=es).z=hdr.worldsize; boxs3D(a, b, 1);
592 glColor3ub(150,0,0);
593 glLineWidth(5);
594 boxs(entorient, eo, es);
595 glLineWidth(1);
598 loopv(entgroup) entfocus(entgroup[i], renderentradius(e));
599 if(enthover>=0) entfocus(enthover, renderentradius(e));
602 bool enttoggle(int id)
604 undonext = true;
605 int i = entgroup.find(id);
606 if(i < 0)
607 entadd(id);
608 else
609 entgroup.remove(i);
610 return i < 0;
613 bool hoveringonent(int ent, int orient)
615 if(noentedit()) return false;
616 entorient = orient;
617 if((efocus = enthover = ent) >= 0)
618 return true;
619 efocus = entgroup.empty() ? -1 : entgroup.last();
620 enthover = -1;
621 return false;
624 VAR(entitysurf, 0, 0, 1);
625 VARF(entmoving, 0, 0, 2,
626 if(enthover < 0 || noentedit())
627 entmoving = 0;
628 else if(entmoving == 1)
629 entmoving = enttoggle(enthover);
630 else if(entmoving == 2 && entgroup.find(enthover) < 0)
631 entadd(enthover);
632 if(entmoving > 0)
633 initentdragging = true;
636 void entpush(int *dir)
638 if(noentedit()) return;
639 int d = dimension(entorient);
640 int s = dimcoord(entorient) ? -*dir : *dir;
641 if(entmoving)
643 groupeditpure(e.o[d] += float(s*sel.grid)); // editdrag supplies the undo
645 else
646 groupedit(e.o[d] += float(s*sel.grid));
647 if(entitysurf==1)
648 player->o[d] += float(s*sel.grid);
651 VAR(entautoviewdist, 0, 25, 100);
652 void entautoview(int *dir)
654 if(!haveselent()) return;
655 static int s = 0;
656 vec v(player->o);
657 v.sub(worldpos);
658 v.normalize();
659 v.mul(entautoviewdist);
660 int t = s + *dir;
661 s = abs(t) % entgroup.length();
662 if(t<0 && s>0) s = entgroup.length() - s;
663 entfocus(entgroup[s],
664 v.add(e.o);
665 player->o = v;
669 COMMAND(entautoview, "i");
670 COMMAND(entflip, "");
671 COMMAND(entrotate, "i");
672 COMMAND(entpush, "i");
674 void delent()
676 if(noentedit()) return;
677 groupedit(e.type = ET_EMPTY;);
678 entcancel();
681 int findtype(char *what)
683 for(int i = 0; *et->entname(i); i++) if(strcmp(what, et->entname(i))==0) return i;
684 conoutf("unknown entity type \"%s\"", what);
685 return ET_EMPTY;
688 VAR(entdrop, 0, 2, 3);
690 bool dropentity(entity &e, int drop = -1)
692 vec radius(4.0f, 4.0f, 4.0f);
693 if(drop<0) drop = entdrop;
694 if(e.type == ET_MAPMODEL)
696 model *m = loadmodel(NULL, e.attr2);
697 if(m)
699 vec center;
700 m->boundbox(0, center, radius);
701 rotatebb(center, radius, e.attr1);
702 radius.x += fabs(center.x);
703 radius.y += fabs(center.y);
705 radius.z = 0.0f;
707 switch(drop)
709 case 1:
710 if(e.type != ET_LIGHT && e.type != ET_SPOTLIGHT)
711 dropenttofloor(&e);
712 break;
713 case 2:
714 case 3:
715 int cx = 0, cy = 0;
716 if(sel.cxs == 1 && sel.cys == 1)
718 cx = (sel.cx ? 1 : -1) * sel.grid / 2;
719 cy = (sel.cy ? 1 : -1) * sel.grid / 2;
721 e.o = sel.o.v;
722 int d = dimension(sel.orient), dc = dimcoord(sel.orient);
723 e.o[R[d]] += sel.grid / 2 + cx;
724 e.o[C[d]] += sel.grid / 2 + cy;
725 if(!dc)
726 e.o[D[d]] -= radius[D[d]];
727 else
728 e.o[D[d]] += sel.grid + radius[D[d]];
730 if(drop == 3)
731 dropenttofloor(&e);
732 break;
734 return true;
737 void dropent()
739 if(noentedit()) return;
740 groupedit(dropentity(e));
743 void attachent()
745 if(noentedit()) return;
746 groupedit(attachentity(e));
749 COMMAND(attachent, "");
751 extentity *newentity(bool local, const vec &o, int type, int v1, int v2, int v3, int v4)
753 extentity &e = *et->newentity();
754 e.o = o;
755 e.attr1 = v1;
756 e.attr2 = v2;
757 e.attr3 = v3;
758 e.attr4 = v4;
759 e.attr5 = 0;
760 e.type = type;
761 e.reserved = 0;
762 e.spawned = false;
763 e.inoctanode = false;
764 e.color = vec(1, 1, 1);
765 if(local)
767 switch(type)
769 case ET_MAPMODEL:
770 e.attr4 = e.attr3;
771 e.attr3 = e.attr2;
772 e.attr2 = e.attr1;
773 case ET_PLAYERSTART:
774 e.attr1 = (int)camera1->yaw;
775 break;
777 et->fixentity(e);
779 return &e;
782 void newentity(int type, int a1, int a2, int a3, int a4)
784 extentity *t = newentity(true, player->o, type, a1, a2, a3, a4);
785 dropentity(*t);
786 et->getents().add(t);
787 int i = et->getents().length()-1;
788 t->type = ET_EMPTY;
789 enttoggle(i);
790 makeundoent();
791 entedit(i, e.type = type);
794 void newent(char *what, int *a1, int *a2, int *a3, int *a4)
796 if(noentedit()) return;
797 int type = findtype(what);
798 if(type != ET_EMPTY)
799 newentity(type, *a1, *a2, *a3, *a4);
802 int entcopygrid;
803 vector<entity> entcopybuf;
805 void entcopy()
807 if(noentedit()) return;
808 entcopygrid = sel.grid;
809 entcopybuf.setsize(0);
810 loopv(entgroup)
811 entfocus(entgroup[i], entcopybuf.add(e).o.sub(sel.o.v));
814 void entpaste()
816 if(noentedit()) return;
817 if(entcopybuf.length()==0) return;
818 entcancel();
819 int last = et->getents().length()-1;
820 float m = float(sel.grid)/float(entcopygrid);
821 loopv(entcopybuf)
823 entity &c = entcopybuf[i];
824 vec o(c.o);
825 o.mul(m).add(sel.o.v);
826 extentity *e = newentity(true, o, ET_EMPTY, c.attr1, c.attr2, c.attr3, c.attr4);
827 et->getents().add(e);
828 entadd(++last);
830 int j = 0;
831 groupeditundo(e.type = entcopybuf[j++].type;);
834 COMMAND(newent, "siiii");
835 COMMAND(delent, "");
836 COMMAND(dropent, "");
837 COMMAND(entcopy, "");
838 COMMAND(entpaste, "");
840 void entset(char *what, int *a1, int *a2, int *a3, int *a4)
842 if(noentedit()) return;
843 int type = findtype(what);
844 groupedit(e.type=type;
845 e.attr1=*a1;
846 e.attr2=*a2;
847 e.attr3=*a3;
848 e.attr4=*a4;);
851 ICOMMAND(enthavesel,"", (), addimplicit(intret(entgroup.length())));
852 ICOMMAND(entselect, "s", (char *body), if(!noentedit()) addgroup(e.type != ET_EMPTY && entgroup.find(n)<0 && execute(body)>0));
853 ICOMMAND(entloop, "s", (char *body), if(!noentedit()) addimplicit(groupeditloop(((void)e, execute(body)))));
854 ICOMMAND(insel, "", (), entfocus(efocus, intret(pointinsel(sel, e.o))));
855 ICOMMAND(entget, "", (), entfocus(efocus, s_sprintfd(s)("%s %d %d %d %d", et->entname(e.type), e.attr1, e.attr2, e.attr3, e.attr4); result(s)));
856 COMMAND(entset, "siiii");
859 int findentity(int type, int index)
861 const vector<extentity *> &ents = et->getents();
862 for(int i = index; i<ents.length(); i++) if(ents[i]->type==type) return i;
863 loopj(min(index, ents.length())) if(ents[j]->type==type) return j;
864 return -1;
867 int spawncycle = -1, fixspawn = 4;
869 void findplayerspawn(dynent *d, int forceent) // place at random spawn. also used by monsters!
871 int pick = forceent;
872 if(pick<0)
874 int r = fixspawn-->0 ? 7 : rnd(10)+1;
875 loopi(r) spawncycle = findentity(ET_PLAYERSTART, spawncycle+1);
876 pick = spawncycle;
878 if(pick!=-1)
880 d->pitch = 0;
881 d->roll = 0;
882 for(int attempt = pick;;)
884 d->o = et->getents()[attempt]->o;
885 d->yaw = et->getents()[attempt]->attr1;
886 if(entinmap(d, true)) break;
887 attempt = findentity(ET_PLAYERSTART, attempt+1);
888 if(attempt<0 || attempt==pick)
890 d->o = et->getents()[attempt]->o;
891 d->yaw = et->getents()[attempt]->attr1;
892 entinmap(d);
893 break;
897 else
899 d->o.x = d->o.y = d->o.z = 0.5f*getworldsize();
900 entinmap(d);
904 void splitocta(cube *c, int size)
906 if(size <= VVEC_INT_MASK+1) return;
907 loopi(8)
909 if(!c[i].children) c[i].children = newcubes(isempty(c[i]) ? F_EMPTY : F_SOLID);
910 splitocta(c[i].children, size>>1);
914 void resetmap()
916 clearoverrides();
917 clearmapsounds();
918 cleanreflections();
919 resetlightmaps();
920 clearparticles();
921 clearsleep();
922 cancelsel();
923 pruneundos();
925 setvar("gamespeed", 100);
926 setvar("paused", 0);
928 et->getents().deletecontentsp();
931 void startmap(const char *name)
933 cl->startmap(name);
936 bool emptymap(int scale, bool force) // main empty world creation routine
938 if(!force && !editmode)
940 conoutf("newmap only allowed in edit mode");
941 return false;
944 resetmap();
946 strncpy(hdr.head, "OCTA", 4);
947 hdr.version = MAPVERSION;
948 hdr.headersize = sizeof(header);
949 hdr.worldsize = 1 << (scale<10 ? 10 : (scale>20 ? 20 : scale));
951 s_strncpy(hdr.maptitle, "Untitled Map by Unknown", 128);
952 hdr.waterlevel = -100000;
953 memset(hdr.watercolour, 0, sizeof(hdr.watercolour));
954 hdr.maple = 8;
955 hdr.mapprec = 32;
956 hdr.mapllod = 0;
957 hdr.lightmaps = 0;
958 memset(hdr.skylight, 0, sizeof(hdr.skylight));
959 memset(hdr.reserved, 0, sizeof(hdr.reserved));
960 texmru.setsize(0);
961 freeocta(worldroot);
962 worldroot = newcubes(F_EMPTY);
963 loopi(4) solidfaces(worldroot[i]);
965 if(hdr.worldsize > VVEC_INT_MASK+1) splitocta(worldroot, hdr.worldsize>>1);
967 clearlights();
968 allchanged();
970 overrideidents = true;
971 execfile("data/default_map_settings.cfg");
972 overrideidents = false;
974 startmap("");
975 player->o.z += player->eyeheight+1;
977 return true;
980 bool enlargemap(bool force)
982 if(!force && !editmode)
984 conoutf("mapenlarge only allowed in edit mode");
985 return false;
987 if(hdr.worldsize >= 1<<20) return false;
989 hdr.worldsize *= 2;
990 cube *c = newcubes(F_EMPTY);
991 c[0].children = worldroot;
992 loopi(3) solidfaces(c[i+1]);
993 worldroot = c;
995 if(hdr.worldsize > VVEC_INT_MASK+1) splitocta(worldroot, hdr.worldsize>>1);
997 allchanged();
999 return true;
1002 void newmap(int *i) { if(emptymap(*i, false)) cl->newmap(max(*i, 0)); }
1003 void mapenlarge() { if(enlargemap(false)) cl->newmap(-1); }
1004 COMMAND(newmap, "i");
1005 COMMAND(mapenlarge, "");
1007 void mapname()
1009 result(cl->getclientmap());
1012 COMMAND(mapname, "");
1014 void mpeditent(int i, const vec &o, int type, int attr1, int attr2, int attr3, int attr4, bool local)
1016 if(et->getents().length()<=i)
1018 while(et->getents().length()<i) et->getents().add(et->newentity())->type = ET_EMPTY;
1019 extentity *e = newentity(local, o, type, attr1, attr2, attr3, attr4);
1020 et->getents().add(e);
1021 addentity(i);
1022 attachentity(*e);
1024 else
1026 extentity &e = *et->getents()[i];
1027 removeentity(i);
1028 int oldtype = e.type;
1029 if(oldtype!=type) detachentity(e);
1030 e.type = type;
1031 e.o = o;
1032 e.attr1 = attr1; e.attr2 = attr2; e.attr3 = attr3; e.attr4 = attr4;
1033 addentity(i);
1034 if(oldtype!=type) attachentity(e);
1038 int getworldsize() { return hdr.worldsize; }
1039 int getmapversion() { return hdr.version; }
1041 int triggertypes[NUMTRIGGERTYPES] =
1044 TRIG_ONCE,
1045 TRIG_RUMBLE,
1046 TRIG_TOGGLE,
1047 TRIG_TOGGLE | TRIG_RUMBLE,
1048 TRIG_MANY,
1049 TRIG_MANY | TRIG_RUMBLE,
1050 TRIG_MANY | TRIG_TOGGLE,
1051 TRIG_MANY | TRIG_TOGGLE | TRIG_RUMBLE,
1052 TRIG_COLLIDE | TRIG_TOGGLE | TRIG_RUMBLE,
1053 TRIG_COLLIDE | TRIG_TOGGLE | TRIG_AUTO_RESET | TRIG_RUMBLE,
1054 TRIG_COLLIDE | TRIG_TOGGLE | TRIG_LOCKED | TRIG_RUMBLE,
1055 TRIG_DISAPPEAR,
1056 TRIG_DISAPPEAR | TRIG_RUMBLE,
1057 TRIG_DISAPPEAR | TRIG_COLLIDE | TRIG_LOCKED,
1058 0 /* reserved */
1061 void resettriggers()
1063 const vector<extentity *> &ents = et->getents();
1064 loopv(ents)
1066 extentity &e = *ents[i];
1067 if(e.type != ET_MAPMODEL || !e.attr3) continue;
1068 e.triggerstate = TRIGGER_RESET;
1069 e.lasttrigger = 0;
1073 void unlocktriggers(int tag, int oldstate = TRIGGER_RESET, int newstate = TRIGGERING)
1075 const vector<extentity *> &ents = et->getents();
1076 loopv(ents)
1078 extentity &e = *ents[i];
1079 if(e.type != ET_MAPMODEL || !e.attr3) continue;
1080 if(e.attr4 == tag && e.triggerstate == oldstate && checktriggertype(e.attr3, TRIG_LOCKED))
1082 if(newstate == TRIGGER_RESETTING && checktriggertype(e.attr3, TRIG_COLLIDE) && overlapsdynent(e.o, 20)) continue;
1083 e.triggerstate = newstate;
1084 e.lasttrigger = lastmillis;
1085 if(checktriggertype(e.attr3, TRIG_RUMBLE)) et->rumble(e);
1090 void trigger(int *tag, int *state)
1092 if(*state) unlocktriggers(*tag);
1093 else unlocktriggers(*tag, TRIGGERED, TRIGGER_RESETTING);
1096 COMMAND(trigger, "ii");
1098 VAR(triggerstate, -1, 0, 1);
1100 void doleveltrigger(int trigger, int state)
1102 s_sprintfd(aliasname)("level_trigger_%d", trigger);
1103 if(identexists(aliasname))
1105 triggerstate = state;
1106 execute(aliasname);
1110 void checktriggers()
1112 if(player->state != CS_ALIVE) return;
1113 vec o(player->o);
1114 o.z -= player->eyeheight;
1115 const vector<extentity *> &ents = et->getents();
1116 loopv(ents)
1118 extentity &e = *ents[i];
1119 if(e.type != ET_MAPMODEL || !e.attr3) continue;
1120 switch(e.triggerstate)
1122 case TRIGGERING:
1123 case TRIGGER_RESETTING:
1124 if(lastmillis-e.lasttrigger>=1000)
1126 if(e.attr4)
1128 if(e.triggerstate == TRIGGERING) unlocktriggers(e.attr4);
1129 else unlocktriggers(e.attr4, TRIGGERED, TRIGGER_RESETTING);
1131 if(checktriggertype(e.attr3, TRIG_DISAPPEAR)) e.triggerstate = TRIGGER_DISAPPEARED;
1132 else if(e.triggerstate==TRIGGERING && checktriggertype(e.attr3, TRIG_TOGGLE)) e.triggerstate = TRIGGERED;
1133 else e.triggerstate = TRIGGER_RESET;
1135 break;
1136 case TRIGGER_RESET:
1137 if(e.lasttrigger)
1139 if(checktriggertype(e.attr3, TRIG_AUTO_RESET|TRIG_MANY|TRIG_LOCKED) && e.o.dist(o)-player->radius>=(checktriggertype(e.attr3, TRIG_COLLIDE) ? 20 : 12))
1140 e.lasttrigger = 0;
1141 break;
1143 else if(e.o.dist(o)-player->radius>=(checktriggertype(e.attr3, TRIG_COLLIDE) ? 20 : 12)) break;
1144 else if(checktriggertype(e.attr3, TRIG_LOCKED))
1146 if(!e.attr4) break;
1147 doleveltrigger(e.attr4, -1);
1148 e.lasttrigger = lastmillis;
1149 break;
1151 e.triggerstate = TRIGGERING;
1152 e.lasttrigger = lastmillis;
1153 if(checktriggertype(e.attr3, TRIG_RUMBLE)) et->rumble(e);
1154 et->trigger(e);
1155 if(e.attr4) doleveltrigger(e.attr4, 1);
1156 break;
1157 case TRIGGERED:
1158 if(e.o.dist(o)-player->radius<(checktriggertype(e.attr3, TRIG_COLLIDE) ? 20 : 12))
1160 if(e.lasttrigger) break;
1162 else if(checktriggertype(e.attr3, TRIG_AUTO_RESET))
1164 if(lastmillis-e.lasttrigger<6000) break;
1166 else if(checktriggertype(e.attr3, TRIG_MANY))
1168 e.lasttrigger = 0;
1169 break;
1171 else break;
1172 if(checktriggertype(e.attr3, TRIG_COLLIDE) && overlapsdynent(e.o, 20)) break;
1173 e.triggerstate = TRIGGER_RESETTING;
1174 e.lasttrigger = lastmillis;
1175 if(checktriggertype(e.attr3, TRIG_RUMBLE)) et->rumble(e);
1176 et->trigger(e);
1177 if(e.attr4) doleveltrigger(e.attr4, 0);
1178 break;