diff --git a/config/menus.cfg b/config/menus.cfg index 5910e971..e9751616 100755 --- a/config/menus.cfg +++ b/config/menus.cfg @@ -1,3 +1,18 @@ +// standard completions + +alias mapcomplete [complete $arg1 "packages/maps" cgz] + +mapcommands = "map tdm coop dm lms ts ctf pf lss osok tosok htf vip tktf ktf" +loop i (listlen $mapcommands) [ + mapcomplete (at $mapcommands $i) +] + +complete demo "demos" dmo + +alias entcomplete [listcomplete $arg1 "light playerstart clips ammobox grenades health armour akimbo mapmodel trigger ladder ctf-flag sound clip"] + +entcomplete newent + // standard menu definitions newmenu main diff --git a/config/menus_edit.cfg b/config/menus_edit.cfg index 4ee35f91..eab76d8c 100644 --- a/config/menus_edit.cfg +++ b/config/menus_edit.cfg @@ -1,3 +1,10 @@ +alias editent [ + alias enttype (getenttype) + if (! (strcmp enttype "")) [ + saycommand "/entset" $enttype (getentattr 0) (getentattr 1) (getentattr 2) (getentattr 3) + ] +] + newmenu editing menuitem "Mapmodels..." "showmenu Mapmodels" diff --git a/config/resetbinds.cfg b/config/resetbinds.cfg index 5019ac85..1ded413b 100644 --- a/config/resetbinds.cfg +++ b/config/resetbinds.cfg @@ -40,7 +40,7 @@ bind 7 "" bind 8 "vdelta -1" bind 9 "vdelta 1" bind COLON "" -bind SEMICOLON "" +bind SEMICOLON "editent" bind LESS "" bind EQUALS "" bind GREATER "" diff --git a/source/src/clients2c.cpp b/source/src/clients2c.cpp index 4af54535..7583f30c 100644 --- a/source/src/clients2c.cpp +++ b/source/src/clients2c.cpp @@ -536,6 +536,7 @@ void parsemessages(int cn, playerent *d, ucharbuf &p) ents[i].attr4 = getint(p); ents[i].spawned = false; if(ents[i].type==LIGHT || to==LIGHT) calclight(); + if(ents[i].type==SOUND) preloadmapsound(ents[i]); break; } diff --git a/source/src/command.cpp b/source/src/command.cpp index 3edb44d9..735b69e5 100644 --- a/source/src/command.cpp +++ b/source/src/command.cpp @@ -440,14 +440,12 @@ int execute(const char *p) // tab-completion of all idents -static int completesize = 0, completeidx = 0; +static int completesize = -1, completeidx = 0; static playerent *completeplayer = NULL; -static cvector maplist; -static cvector newentlist; void resetcomplete() { - completesize = 0; + completesize = -1; completeplayer = NULL; } @@ -458,9 +456,9 @@ bool nickcomplete(char *s) if(!players.length()) return false; char *cp = s; - for(int i = (int)strlen(s) - 2; i > 0; i--) + for(int i = (int)strlen(s) - 1; i > 0; i--) if(s[i] == ' ') { cp = s + i + 1; break; } - if(!completesize) { completesize = (int)strlen(cp); completeidx = 0; } + if(completesize < 0) { completesize = (int)strlen(cp); completeidx = 0; } int idx = 0; if(completeplayer!=NULL) @@ -483,6 +481,86 @@ bool nickcomplete(char *s) return false; } +enum { COMPLETE_FILE = 0, COMPLETE_LIST, COMPLETE_NICK }; + +struct completekey +{ + int type; + const char *dir, *ext; + + completekey() {} + completekey(int type, const char *dir, const char *ext) : type(type), dir(dir), ext(ext) {} +}; + +struct completeval +{ + int type; + char *dir, *ext; + vector list; + + completeval(int type, const char *dir, const char *ext) : type(type), dir(dir && dir[0] ? newstring(dir) : NULL), ext(ext && ext[0] ? newstring(ext) : NULL) {} + ~completeval() { DELETEA(dir); DELETEA(ext); list.deletecontentsa(); } +}; + +static inline bool htcmp(const completekey &x, const completekey &y) +{ + return x.type==y.type && (x.dir == y.dir || (x.dir && y.dir && !strcmp(x.dir, y.dir))) && (x.ext == y.ext || (x.ext && y.ext && !strcmp(x.ext, y.ext))); +} + +static inline uint hthash(const completekey &k) +{ + return k.dir ? hthash(k.dir) + k.type : k.type; +} + +static hashtable completedata; +static hashtable completions; + +void addcomplete(char *command, int type, char *dir, char *ext) +{ + if(type==COMPLETE_FILE) + { + int dirlen = (int)strlen(dir); + while(dirlen > 0 && (dir[dirlen-1] == '/' || dir[dirlen-1] == '\\')) + dir[--dirlen] = '\0'; + if(ext) + { + if(strchr(ext, '*')) ext[0] = '\0'; + if(!ext[0]) ext = NULL; + } + } + completekey key(type, dir, ext); + completeval **val = completedata.access(key); + if(!val) + { + completeval *f = new completeval(type, dir, ext); + if(type==COMPLETE_LIST) explodelist(dir, f->list); + val = &completedata[completekey(type, f->dir, f->ext)]; + *val = f; + } + completeval **hascomplete = completions.access(command); + if(hascomplete) *hascomplete = *val; + else completions[newstring(command)] = *val; +} + +void addfilecomplete(char *command, char *dir, char *ext) +{ + addcomplete(command, COMPLETE_FILE, dir, ext); +} + +void addlistcomplete(char *command, char *list) +{ + addcomplete(command, COMPLETE_LIST, list, NULL); +} + +void addnickcomplete(char *command) +{ + addcomplete(command, COMPLETE_NICK, NULL, NULL); +} + +COMMANDN(complete, addfilecomplete, ARG_3STR); +COMMANDN(listcomplete, addlistcomplete, ARG_2STR); +COMMANDN(nickcomplete, addnickcomplete, ARG_1STR); + void commandcomplete(char *s) { if(*s!='/') @@ -494,23 +572,31 @@ void commandcomplete(char *s) } if(!s[1]) return; char *cp = s; - for(int i = (int)strlen(s) - 2; i > 0; i--) + for(int i = (int)strlen(s) - 1; i > 0; i--) if(s[i] == ';' || s[i] == ' ') { cp = s + i; break; } - if(!completesize) + bool init = false; + if(completesize < 0) { completesize = (int)strlen(cp)-1; completeidx = 0; - if(*cp == ' ') - { - maplist.setsize(0); - listfiles("packages/maps", "cgz", maplist); - if(!newentlist.length()) - { - const char *neargs[] = { "light", "health", "armour", "akimbo", "grenades", "clips", "ammobox", "playerstart", "mapmodel", "ctf-flag", "ladder", "sound", "" }; - for(int i = 0; strlen(neargs[i]); i++) newentlist.add(newstring(neargs[i])); - } - } + if(*cp == ' ') init = true; } + + completeval *cdata = NULL; + char *end = strchr(s, ' '); + if(end && end <= cp) + { + string command; + s_strncpy(command, s+1, min(size_t(end-s), sizeof(command))); + completeval **hascomplete = completions.access(command); + if(hascomplete) cdata = *hascomplete; + } + if(init && cdata && cdata->type==COMPLETE_FILE) + { + cdata->list.deletecontentsa(); + listfiles(cdata->dir, cdata->ext, cdata->list); + } + if(*cp == '/' || *cp == ';') { // commandname completion int idx = 0; @@ -524,20 +610,23 @@ void commandcomplete(char *s) completeidx++; if(completeidx>=idx) completeidx = 0; } - else + else if(!cdata) return; + else if(cdata->type==COMPLETE_NICK) nickcomplete(s); + else { // argument completion - cvector &list = strncmp(s, "/newent", 7) ? maplist : newentlist; - loopv(list) + loopv(cdata->list) { - int j = (i + completeidx) % list.length(); - if(!_strncmpi(list[j], cp + 1, completesize)) + int j = (i + completeidx) % cdata->list.length(); + if(!_strncmpi(cdata->list[j], cp + 1, completesize)) { cp[1] = '\0'; - s_strcat(s, list[j]); + s_strcat(s, cdata->list[j]); completeidx = j + 1; break; } } + completeidx++; + if(completeidx >= cdata->list.length()) completeidx = 0; } } diff --git a/source/src/editing.cpp b/source/src/editing.cpp index 47cb6e90..d60a708e 100644 --- a/source/src/editing.cpp +++ b/source/src/editing.cpp @@ -481,7 +481,7 @@ void edittag(int tag) void newent(char *what, char *a1, char *a2, char *a3, char *a4) { EDITSEL; - newentity(sel.x, sel.y, (int)camera1->o.z, what, ATOI(a1), ATOI(a2), ATOI(a3), ATOI(a4)); + newentity(-1, sel.x, sel.y, (int)camera1->o.z, what, ATOI(a1), ATOI(a2), ATOI(a3), ATOI(a4)); } COMMANDN(select, selectpos, ARG_4INT); diff --git a/source/src/entities.cpp b/source/src/entities.cpp index ebe16613..655a665a 100644 --- a/source/src/entities.cpp +++ b/source/src/entities.cpp @@ -11,7 +11,8 @@ const char *entnames[] = "clips", "ammobox","grenades", "health", "armour", "akimbo", "mapmodel", "trigger", - "ladder", "ctf-flag", "sound", "?", "?", + "ladder", "ctf-flag", + "sound", "clip", "", "" }; const char *entmdlnames[] = @@ -27,6 +28,34 @@ void renderent(entity &e) rendermodel(mdlname, ANIM_MAPMODEL|ANIM_LOOP|ANIM_DYNALLOC, 0, 0, vec(e.x, e.y, z+S(e.x, e.y)->floor+e.attr1), yaw, 0); } +void renderclip(entity &e) +{ + float radius = max(float(e.attr2), 1.0f); + vec bbmin(e.x - radius, e.y - radius, float(S(e.x, e.y)->floor+e.attr1)), + bbmax(e.x + radius, e.y + radius, bbmin.z + max(float(e.attr3), 1.0f)); + + glDisable(GL_TEXTURE_2D); + linestyle(1, 0xFF, 0xFF, 0); + glBegin(GL_LINES); + + glVertex3f(bbmin.x, bbmin.y, bbmin.z); + loopi(2) glVertex3f(bbmax.x, bbmin.y, bbmin.z); + loopi(2) glVertex3f(bbmax.x, bbmax.y, bbmin.z); + loopi(2) glVertex3f(bbmin.x, bbmax.y, bbmin.z); + glVertex3f(bbmin.x, bbmin.y, bbmin.z); + + glVertex3f(bbmin.x, bbmin.y, bbmax.z); + loopi(2) glVertex3f(bbmax.x, bbmin.y, bbmax.z); + loopi(2) glVertex3f(bbmax.x, bbmax.y, bbmax.z); + loopi(2) glVertex3f(bbmin.x, bbmax.y, bbmax.z); + glVertex3f(bbmin.x, bbmin.y, bbmax.z); + + loopi(8) glVertex3f(i&2 ? bbmax.x : bbmin.x, i&4 ? bbmax.y : bbmin.y, i&1 ? bbmax.z : bbmin.z); + + glEnd(); + glEnable(GL_TEXTURE_2D); +} + void renderentities() { if(editmode && !reflecting && !refracting && !stenciling) @@ -35,13 +64,14 @@ void renderentities() if(lastmillis - lastsparkle >= 20) { lastsparkle = lastmillis - (lastmillis%20); + int closest = closestent(); loopv(ents) { entity &e = ents[i]; if(e.type==NOTUSED) continue; vec v(e.x, e.y, e.z); if(vec(v).sub(camera1->o).dot(camdir) < 0) continue; - particle_splash(2, 2, 40, v); + particle_splash(i == closest ? 12 : 2, 2, 40, v); } } } @@ -61,10 +91,14 @@ void renderentities() renderent(e); } } - else if(e.type==CTF_FLAG && editmode) + else if(editmode) { - s_sprintfd(path)("pickups/flags/%s", team_string(e.attr2)); - rendermodel(path, ANIM_FLAG|ANIM_LOOP, 0, 0, vec(e.x, e.y, (float)S(e.x, e.y)->floor), (float)((e.attr1+7)-(e.attr1+7)%15), 0, 120.0f); + if(e.type==CTF_FLAG) + { + s_sprintfd(path)("pickups/flags/%s", team_string(e.attr2)); + rendermodel(path, ANIM_FLAG|ANIM_LOOP, 0, 0, vec(e.x, e.y, (float)S(e.x, e.y)->floor), (float)((e.attr1+7)-(e.attr1+7)%15), 0, 120.0f); + } + else if(e.type==CLIP) renderclip(e); } } if(m_flags) loopi(2) diff --git a/source/src/entity.h b/source/src/entity.h index 1c6e5c18..84a24a8d 100644 --- a/source/src/entity.h +++ b/source/src/entity.h @@ -3,13 +3,14 @@ enum // static entity types NOTUSED = 0, // entity slot not in use in map LIGHT, // lightsource, attr1 = radius, attr2 = intensity PLAYERSTART, // attr1 = angle, attr2 = team - I_CLIPS, I_AMMO,I_GRENADE, + I_CLIPS, I_AMMO, I_GRENADE, I_HEALTH, I_ARMOUR, I_AKIMBO, MAPMODEL, // attr1 = angle, attr2 = idx CARROT, // attr1 = tag, attr2 = type LADDER, CTF_FLAG, // attr1 = angle, attr2 = red/blue SOUND, + CLIP, MAXENTTYPES }; diff --git a/source/src/physics.cpp b/source/src/physics.cpp index 9de0a43f..8d1acbcb 100644 --- a/source/src/physics.cpp +++ b/source/src/physics.cpp @@ -114,17 +114,31 @@ bool mmcollide(physent *d, float &hi, float &lo) // collide with a map loopv(ents) { entity &e = ents[i]; - if(e.type!=MAPMODEL) continue; - mapmodelinfo &mmi = getmminfo(e.attr2); - if(!&mmi || !mmi.h) continue; - const float r = mmi.rad+d->radius; - if(fabs(e.x-d->o.x)o.y)floor+mmi.zoff+e.attr3); - const float dz = d->o.z-eyeheight; - if(dzlo) lo = mmz+mmi.h; - if(hi-lo < playerheight) return false; + const float r = max(float(e.attr2), 1.0f) + d->radius; + if(fabs(e.x-d->o.x)o.y)floor+e.attr1), ch = max(float(e.attr3), 1.0f); + const float dz = d->o.z-d->eyeheight; + if(dzlo) lo = cz+ch; + if(hi-lo < playerheight) return false; + } + } + else if(e.type==MAPMODEL) + { + mapmodelinfo &mmi = getmminfo(e.attr2); + if(!&mmi || !mmi.h) continue; + const float r = mmi.rad+d->radius; + if(fabs(e.x-d->o.x)o.y)floor+mmi.zoff+e.attr3); + const float dz = d->o.z-eyeheight; + if(dzlo) lo = mmz+mmi.h; + if(hi-lo < playerheight) return false; + } } } return true; diff --git a/source/src/protos.h b/source/src/protos.h index a17fea5f..8c641ab9 100644 --- a/source/src/protos.h +++ b/source/src/protos.h @@ -383,7 +383,7 @@ extern void remipmore(const block &b, int level = 0); extern int closestent(); extern int findentity(int type, int index = 0); extern int findentity(int type, int index, uchar attr2); -extern entity *newentity(int x, int y, int z, char *what, int v1, int v2, int v3, int v4); +extern entity *newentity(int index, int x, int y, int z, char *what, int v1, int v2, int v3, int v4); // worldlight extern int lastcalclight; diff --git a/source/src/renderparticles.cpp b/source/src/renderparticles.cpp index 5df5888e..0ea9fde8 100644 --- a/source/src/renderparticles.cpp +++ b/source/src/renderparticles.cpp @@ -185,7 +185,7 @@ void cleanupexplosion() } } -#define MAXPARTYPES 12 +#define MAXPARTYPES 13 struct particle { vec o, d; int fade, type; int millis; particle *next; }; particle *parlist[MAXPARTYPES], *parempty = NULL; @@ -263,6 +263,7 @@ static struct parttype { int type; float r, g, b; int gr, tex; float sz; } partt { PT_DECAL, 1.0f, 1.0f, 1.0f, 0, 5, 1.5f }, // scorch decal { PT_HUDFLASH, 1.0f, 1.0f, 1.0f, 0, 6, 0.7f }, // hudgun muzzle flash { PT_FLASH, 1.0f, 1.0f, 1.0f, 0, 6, 0.7f }, // muzzle flash + { PT_PART, 1.0f, 0.5f, 0.2f, 20, 0, 0.08f }, // orange: edit mode closest ent }; VAR(particlesize, 20, 100, 500); diff --git a/source/src/world.cpp b/source/src/world.cpp index ce9509ac..56f91635 100644 --- a/source/src/world.cpp +++ b/source/src/world.cpp @@ -158,6 +158,31 @@ void entproperty(int prop, int amount) hashtable mapinfo, &resdata = mapinfo; +void getenttype() +{ + int e = closestent(); + if(e<0) return; + int type = ents[e].type; + if(type < 0 || type >= MAXENTTYPES) return; + result(entnames[type]); +} + +int getentattr(int attr) +{ + int e = closestent(); + if(e>=0) switch(attr) + { + case 0: return ents[e].attr1; + case 1: return ents[e].attr2; + case 2: return ents[e].attr3; + case 3: return ents[e].attr4; + } + return 0; +} + +COMMAND(getenttype, ARG_NONE); +COMMAND(getentattr, ARG_1EXP); + void delent() { int e = closestent(); @@ -179,7 +204,7 @@ int findtype(char *what) return NOTUSED; } -entity *newentity(int x, int y, int z, char *what, int v1, int v2, int v3, int v4) +entity *newentity(int index, int x, int y, int z, char *what, int v1, int v2, int v3, int v4) { int type = findtype(what); if(type==NOTUSED) return NULL; @@ -188,8 +213,13 @@ entity *newentity(int x, int y, int z, char *what, int v1, int v2, int v3, int v switch(type) { + case CLIP: + if(v2<=0) e.attr2 = 1; + if(v3<=0) e.attr3 = 1; + break; + case LIGHT: - if(v1>64) v1 = 64; + if(v1>64) e.attr1 = 64; if(!v1) e.attr1 = 16; if(!v2 && !v3 && !v4) e.attr2 = 255; break; @@ -204,17 +234,39 @@ entity *newentity(int x, int y, int z, char *what, int v1, int v2, int v3, int v e.attr1 = (int)camera1->yaw; break; } - addmsg(SV_EDITENT, "ri9", ents.length(), type, e.x, e.y, e.z, e.attr1, e.attr2, e.attr3, e.attr4); + addmsg(SV_EDITENT, "ri9", index<0 ? ents.length() : index, type, e.x, e.y, e.z, e.attr1, e.attr2, e.attr3, e.attr4); e.spawned = true; - ents.add(e); + int oldtype = type; + if(index<0) ents.add(e); + else + { + oldtype = ents[index].type; + ents[index] = e; + } + if(oldtype!=type) switch(oldtype) + { + case LIGHT: calclight(); break; + } switch(type) { case LIGHT: calclight(); break; case SOUND: preloadmapsound(e); break; } - return &ents.last(); + return index<0 ? &ents.last() : &ents[index]; } +void entset(char *what, char *a1, char *a2, char *a3, char *a4) +{ + int n = closestent(); + if(n>=0) + { + entity &e = ents[n]; + newentity(n, e.x, e.y, e.z, what, ATOI(a1), ATOI(a2), ATOI(a3), ATOI(a4)); + } +} + +COMMAND(entset, ARG_5STR); + void clearents(char *name) { int type = findtype(name);