diff --git a/source/src/audiomanager.cpp b/source/src/audiomanager.cpp index 98a641c0..7377cdea 100644 --- a/source/src/audiomanager.cpp +++ b/source/src/audiomanager.cpp @@ -835,3 +835,70 @@ COMMANDF(music, "sis", (char *name, int *millis, char *cmd) audiomgr.music(name, *millis, cmd); }); +void mapsoundslotusage(int *n) // returns all entity indices that use a mapsound of slot n +{ + string res = ""; + loopv(ents) if(ents[i].type == SOUND && ents[i].attr1 == *n) concatformatstring(res, "%s%d", i ? " " : "", i); + result(res); +} +COMMAND(mapsoundslotusage, "i"); + +void reloadmapsoundconfig() +{ + vector sc; + getcurrentmapconfig(sc, true); // only soundconfig + sc.add('\0'); + execute(sc.getbuf()); // cheap and easy way ;) +} + +void deletemapsoundslot(int *n, char *opt) // delete mapsound slot - only if unused or "purge" is specified +{ + if(noteditmode("deletemapsoundslot") || multiplayer(true) || !mapconfigdata.mapsoundlines.inrange(*n)) return; + bool purgeall = !strcmp(opt, "purge"), slotused = false; + loopv(ents) if(ents[i].type == SOUND && ents[i].attr1 == *n) slotused = true; + if(!purgeall && slotused) { conoutf("mapsound slot #%d is in use: can't delete", *n); return; } + audiomgr.mapsoundreset(); + int deld = 0; + loopv(ents) if(ents[i].type == SOUND) + { + entity &e = ents[i]; + if(e.attr1 == *n) + { // delete entity + deletesoundentity(e); + memset(&e, 0, sizeof(persistent_entity)); + e.type = NOTUSED; + deld++; + } + else if(e.attr1 > *n) e.attr1--; // adjust models in higher slots + } + mapconfigdata.mapsoundlines.remove(*n); + reloadmapsoundconfig(); + defformatstring(s)(" (%d mapsounds purged)", deld); + conoutf("mapsound slot #%d deleted%s", *n, deld ? s : ""); + mapsoundchanged = 1; + unsavededits++; + hdr.flags |= MHF_AUTOMAPCONFIG; // requires automapcfg +} +COMMAND(deletemapsoundslot, "is"); + +void editmapsoundslot(int *n, char *name, char *maxuses) // edit slot parameters != "" +{ + string res = ""; + if(!noteditmode("editmapsoundslot") && !multiplayer(true) && mapconfigdata.mapsoundlines.inrange(*n)) + { + mapsoundline &msl = mapconfigdata.mapsoundlines[*n]; + if(*name || *maxuses) + { // change attributes + if(*maxuses) msl.maxuses = strtol(maxuses, NULL, 0); + if(*name) copystring(msl.name, name); + reloadmapsoundconfig(); + mapsoundchanged = 1; + unsavededits++; + hdr.flags |= MHF_AUTOMAPCONFIG; // requires automapcfg + } + formatstring(res)("\"%s\" %d", msl.name, msl.maxuses); // give back all current attributes + } + result(res); +} +COMMAND(editmapsoundslot, "isssss"); + diff --git a/source/src/client.cpp b/source/src/client.cpp index f7a790b8..a17ebb4c 100644 --- a/source/src/client.cpp +++ b/source/src/client.cpp @@ -1010,6 +1010,9 @@ int progress_callback_dlpackage(void *clientp, double dltotal, double dlnow, dou return 0; } +VAR(rereadtexturelists, 0, 1, 1); // flag to indicate additional texture files to trigger a menu rebuild (defaults to "1" to build the menus after startup) +VAR(rereadsoundlists, 0, 1, 1); // same for map sounds + int processdownload(package *pck) { string tmpname = ""; @@ -1023,6 +1026,8 @@ int processdownload(package *pck) const char *pckname = findfile(path(pck->name, true), "w+"); // with textures/sounds, the image/audio file itself is sent. Just need to copy it from the temporary file if(rename(tmpname, pckname)) conoutf(_("\f3failed to install"), pckname); + if(pck->type == PCK_TEXTURE) rereadtexturelists = 1; + else rereadsoundlists = 1; break; } @@ -1037,6 +1042,7 @@ int processdownload(package *pck) char *fname = newstring(pck->name), *ls = strrchr(fname, '/'); if(ls) *ls = '\0'; addzip(tmpname, fname, NULL, true, pck->type); + rereadtexturelists = 1; break; } diff --git a/source/src/protos.h b/source/src/protos.h index d61a3490..ead6f6b3 100644 --- a/source/src/protos.h +++ b/source/src/protos.h @@ -485,6 +485,7 @@ extern bool empty_world(int factor, bool force); extern void remip(const block &b, int level = 0); extern void remipmore(const block &b, int level = 0); extern int closestent(); +extern void deletesoundentity(entity &e); extern int findtype(char *what); extern int findentity(int type, int index = 0); extern int findentity(int type, int index, uchar attr2); diff --git a/source/src/rendermodel.cpp b/source/src/rendermodel.cpp index 24d23b96..f943018d 100644 --- a/source/src/rendermodel.cpp +++ b/source/src/rendermodel.cpp @@ -148,6 +148,63 @@ COMMANDF(mapmodelslotbyname, "s", (char *name) // returns the slot(s) that a cer result(res); }); +void mapmodelslotusage(int *n) // returns all entity indices that contain a mapmodel of slot n +{ + string res = ""; + loopv(ents) if(ents[i].type == MAPMODEL && ents[i].attr2 == *n) concatformatstring(res, "%s%d", i ? " " : "", i); + result(res); +} +COMMAND(mapmodelslotusage, "i"); + +void deletemapmodelslot(int *n, char *opt) // delete mapmodel slot - only if unused or "purge" is specified +{ + if(noteditmode("deletemapmodelslot") || multiplayer(true) || !mapmodels.inrange(*n)) return; + bool purgeall = !strcmp(opt, "purge"); + if(!purgeall) loopv(ents) if(ents[i].type == MAPMODEL && ents[i].attr2 == *n) { conoutf("mapmodel slot #%d is in use: can't delete", *n); return; } + int deld = 0; + loopv(ents) if(ents[i].type == MAPMODEL) + { + entity &e = ents[i]; + if(e.attr2 == *n) + { // delete entity + memset(&e, 0, sizeof(persistent_entity)); + e.type = NOTUSED; + deld++; + } + else if(e.attr2 > *n) e.attr2--; // adjust models in higher slots + } + mapmodels.remove(*n); + defformatstring(s)(" (%d mapmodels purged)", deld); + conoutf("mapmodel slot #%d deleted%s", *n, deld ? s : ""); + unsavededits++; + mapmodelchanged = 1; + hdr.flags |= MHF_AUTOMAPCONFIG; // requires automapcfg +} +COMMAND(deletemapmodelslot, "is"); + +void editmapmodelslot(int *n, char *rad, char *h, char *zoff, char *snap, char *name) // edit slot parameters != "" +{ + string res = ""; + if(!noteditmode("editmapmodelslot") && !multiplayer(true) && mapmodels.inrange(*n)) + { + mapmodelinfo &mmi = mapmodels[*n]; + if(*rad || *h || *zoff || *snap || *name) + { // change attributes + if(*rad) mmi.rad = strtol(rad, NULL, 0); + if(*h) mmi.h = strtol(h, NULL, 0); + if(*zoff) mmi.zoff = strtol(zoff, NULL, 0); + mmi.m = NULL; + if(*name) formatstring(mmi.name)("%s%s", mmpath, name); + unsavededits++; + mapmodelchanged = 1; + hdr.flags |= MHF_AUTOMAPCONFIG; // requires automapcfg + } + formatstring(res)("%d %d %d 0 \"%s\"", mmi.rad, mmi.h, mmi.zoff, mmshortname(mmi.name)); // give back all current attributes + } + result(res); +} +COMMAND(editmapmodelslot, "isssss"); + hashtable mdlregistry; void setmodelattributes(const char *name, mapmodelattributes &ma) diff --git a/source/src/texture.cpp b/source/src/texture.cpp index 5c088c21..420697e2 100644 --- a/source/src/texture.cpp +++ b/source/src/texture.cpp @@ -497,35 +497,44 @@ COMMANDF(texturereset, "", () } }); -void texture(float *scale, char *name) +void _texture(Slot &s, float *scale, char *name) +{ + if(name) + { + filtertext(s.name, parentdir(name), FTXT__MEDIAFILEPATH); // filter parts separately, because filename may (legally) contain "" + if(*s.name) concatstring(s.name, "/"); + name = (char *)behindpath(name); + if(*name == '<') + { + char *endcmd = strchr(name, '>'); + if(endcmd) + { + *endcmd = '\0'; + concatstring(s.name, name); + concatstring(s.name, ">"); + name = endcmd + 1; + } + } + filtertext(name, name, FTXT__MEDIAFILENAME); + concatstring(s.name, name); + } + s.tex = NULL; + s.loaded = false; + if(scale) + { + s.orgscale = *scale; + s.scale = (*scale > 0 && *scale <= 2.0f) ? *scale : 1.0f; + } +} + +COMMANDF(texture, "fs", (float *scale, char *name) { intret(slots.length()); Slot &s = slots.add(); - filtertext(s.name, parentdir(name), FTXT__MEDIAFILEPATH); // filter parts separately, because filename may (legally) contain "" - if(*s.name) concatstring(s.name, "/"); - name = (char *)behindpath(name); - if(*name == '<') - { - char *endcmd = strchr(name, '>'); - if(endcmd) - { - *endcmd = '\0'; - concatstring(s.name, name); - concatstring(s.name, ">"); - name = endcmd + 1; - } - } - filtertext(name, name, FTXT__MEDIAFILENAME); - concatstring(s.name, name); - - s.tex = NULL; - s.loaded = false; - s.orgscale = *scale; - s.scale = (*scale > 0 && *scale <= 2.0f) ? *scale : 1.0f; + _texture(s, scale, name); flagmapconfigchange(); -} +}); -COMMAND(texture, "fs"); const char *gettextureslot(int i) { @@ -926,3 +935,146 @@ void guidetoggle() } COMMAND(guidetoggle, ""); + +void gettexturelist(char *_filter, char *_exclude, char *_exts) // create a list of texture paths and filenames +{ + vector filter, exclude, exts, files; + vector filter_n, exclude_n, exts_n; + explodelist(_filter, filter); // path has to start with one of these + explodelist(_exclude, exclude); // path may not start with one of these + explodelist(_exts, exts); // file needs to have one of those extensions (or ".jpg" by default) + if(!exts.length()) exts.add(newstring(".jpg")); + loopv(filter) filter_n.add((int) strlen(filter[i])); + loopv(exclude) exclude_n.add((int) strlen(exclude[i])); + loopv(exts) exts_n.add((int) strlen(exts[i])); + listfilesrecursive("packages/textures", files); + files.sort(stringsort); + loopvrev(files) if(files.inrange(i + 1) && !strcmp(files[i], files[i + 1])) delstring(files.remove(i + 1)); // remove doubles + int pn = strlen("packages/textures/"); + loopvrev(files) + { + bool filtered = filter.length() == 0, excluded = false, extmatch = false; + if(!strncmp(files[i], "packages/textures/", pn)) + { + const char *s = files[i] + pn; + loopvj(filter) if(!strncmp(s, filter[j], filter_n[j])) filtered = true; + loopvj(exclude) if(!strncmp(s, exclude[j], exclude_n[j])) excluded = true; + int n = strlen(s); + loopvj(exts) if(n > exts_n[j] && !strcmp(s + n - exts_n[j], exts[j])) extmatch = true; + } + if(!extmatch || !filtered || excluded) delstring(files.remove(i)); + } + vector res; + bool cutprefix = filter.length() == 1; + bool cutext = exts.length() == 1 && exts[0][0] != '.'; + loopv(files) + { + char *f = files[i] += pn; + const char *p = parentdir(f), *b = behindpath(f); + cvecprintf(res, "\"%s\" \"%s\"", p, b); // always add columns for path (without "packages/textures/") and filename + if(cutprefix) cvecprintf(res, " \"%s\"", int(strlen(p)) > filter_n[0] ? p + filter_n[0] : ""); // if there's exactly one filter string, add column with that string omitted + if(cutext) + { + int n = strlen(b); + if(n > exts_n[0]) ((char *)b)[n - exts_n[0]] = '\0'; + cvecprintf(res, " \"%s\"", b); // if there's only one allowed extension and it does not start with '.', add column of filenames without extension + } + res.add('\n'); + } + if(res.length()) res.last() = '\0'; + else res.add('\0'); + filter.deletearrays(); + exclude.deletearrays(); + exts.deletearrays(); + result(res.getbuf()); +} +COMMAND(gettexturelist, "sss"); + +bool testworldtexusage(int n) +{ + sqr *s = world; + loopirev(cubicsize) + { + if(s->wtex == n || s->ctex == n || s->ftex == n || s->utex == n) return true; + s++; + } + return false; +} + +void textureslotusage(int *n) // returns all mapmodel entity indices that use a certain texture to skin the model + // if the texture is used in map geometry, at least a space is returned + // if the texture is unused, an empty string is returned +{ + string res = ""; + loopv(ents) if(ents[i].type == MAPMODEL && ents[i].attr4 == *n) concatformatstring(res, "%s%d", i ? " " : "", i); + if(!*res) + { // not used to skin a mapmodel: check world usage... + if(testworldtexusage(*n)) copystring(res, " "); + } + result(res); +} +COMMAND(textureslotusage, "i"); + +void deletetextureslot(int *n, char *opt) // delete texture slot - only if unused or "purge" is specified +{ + if(noteditmode("deletetextureslot") || multiplayer(true) || !slots.inrange(*n)) return; + bool purgeall = !strcmp(opt, "purge"); + bool mmused = false; + loopv(ents) if(ents[i].type == MAPMODEL && ents[i].attr4 == *n) mmused = true; + if(!purgeall && *n <= DEFAULT_CEIL) { conoutf("texture slots below #%d should usually not be deleted", DEFAULT_CEIL + 1); return; } + if(!purgeall && (mmused || testworldtexusage(*n))) { conoutf("texture slot #%d is in use: can't delete", *n); return; } + int deld = 0; + sqr *s = world; + loopirev(cubicsize) + { // check, if cubes use the texture + if(s->wtex == *n) { s->wtex = 255; deld++; } + else if(s->wtex > *n) s->wtex--; + if(s->ctex == *n) { s->ctex = 255; deld++; } + else if(s->ctex > *n) s->ctex--; + if(s->ftex == *n) { s->ftex = 255; deld++; } + else if(s->ftex > *n) s->ftex--; + if(s->utex == *n) { s->utex = 255; deld++; } + else if(s->utex > *n) s->utex--; + s++; + } + block bb = { mapdims.x1 - 1, mapdims.y1 - 1, mapdims.xspan + 2, mapdims.yspan + 2 }; + remip(bb); + loopv(ents) if(ents[i].type == MAPMODEL) + { // check, if mapmodels use the texture + entity &e = ents[i]; + if(e.attr4 == *n) + { // use default model texture instead + e.attr4 = 0; + deld++; + } + else if(e.attr4 > *n) e.attr4--; // adjust models in higher slots + } + // FIXME: texlists... + slots.remove(*n); + defformatstring(m)(" (%d uses removed)", deld); + conoutf("texture slot #%d deleted%s", *n, deld ? m : ""); + unsavededits++; + hdr.flags |= MHF_AUTOMAPCONFIG; // requires automapcfg +} +COMMAND(deletetextureslot, "is"); + +void edittextureslot(int *n, char *scale, char *name) // edit slot parameters != "" +{ + string res = ""; + if(!noteditmode("edittextureslot") && !multiplayer(true) && slots.inrange(*n)) + { + Slot &s = slots[*n]; + if(*scale || *name) + { // change attributes + float newscale = atof(scale); + _texture(s, *scale ? &newscale : NULL, *name ? name : NULL); + unsavededits++; + hdr.flags |= MHF_AUTOMAPCONFIG; // requires automapcfg + } + defformatstring(d)("%d", int(s.orgscale)); + formatstring(res)("%s \"%s\"", s.orgscale == int(s.orgscale) ? d : floatstr(s.orgscale), s.name); + } + result(res); +} +COMMAND(edittextureslot, "iss"); + diff --git a/source/src/world.cpp b/source/src/world.cpp index d4a22a28..cf26eded 100644 --- a/source/src/world.cpp +++ b/source/src/world.cpp @@ -171,6 +171,7 @@ void entproperty(int prop, int amount) int n = closestent(); if(n<0) return; entity &e = ents[n]; + int old_a1 = e.attr1; switch(prop) { case 0: e.attr1 += amount; break; @@ -187,7 +188,7 @@ void entproperty(int prop, int amount) case SOUND: audiomgr.preloadmapsound(e); entityreference entref(&e); - location *loc = audiomgr.locations.find(e.attr1-amount, &entref, mapsounds); + location *loc = audiomgr.locations.find(old_a1, &entref, mapsounds); if(loc) loc->drop(); } @@ -222,6 +223,13 @@ void getentattr(int *attr) COMMAND(getenttype, ""); COMMAND(getentattr, "i"); +void deletesoundentity(entity &e) +{ + entityreference entref(&e); + location *loc = audiomgr.locations.find(e.attr1, &entref, mapsounds); + if(loc) loc->drop(); +} + void delent() { int n = closestent(); @@ -234,11 +242,7 @@ void delent() if (t == SOUND) //stop playing sound { - entityreference entref(&e); - location *loc = audiomgr.locations.find(e.attr1, &entref, mapsounds); - - if(loc) - loc->drop(); + deletesoundentity(e); } ents[n].type = NOTUSED; @@ -348,6 +352,70 @@ void clearents(char *name) COMMAND(clearents, "s"); +// entity commands based on entity number (for scripts, singleplayer only) + +void deleteentity(char *ns) +{ + int n = ATOI(ns); + if(noteditmode("deleteentity") || multiplayer(true) || !*ns || !ents.inrange(n)) return; + entity &e = ents[n]; + int t = e.type; + if(t == SOUND) deletesoundentity(e); + conoutf("deleted entity #%d (%s)", n, entnames[e.type]); + memset(&e, 0, sizeof(persistent_entity)); + e.type = NOTUSED; + if(t == LIGHT) calclight(); + unsavededits++; +} +COMMAND(deleteentity, "s"); + +void editentity(char **args, int numargs) // index x y z a1 a2 a3 a4 ... +{ + string res = ""; + if(numargs > 0) + { + int n = ATOI(args[0]); + if(noteditmode("editentity") || multiplayer(true) || !*args[0] || !ents.inrange(n)) return; + entity &e = ents[n]; + bool edit = false; + for(int i = 1; i < numargs; i++) if(*args[i]) edit = true; // only arguments other than empty strings can edit anything - otherwise we're just browsing + if(edit) + { + if(e.type == SOUND) + { // disable sound /before/ changing it + entityreference entref(&e); + location *loc = audiomgr.locations.find(e.attr1, &entref, mapsounds); + if(loc) loc->drop(); + } + for(int i = 1; i < numargs; i++) if(*args[i]) + { + int v = ATOI(args[i]); + switch(i) + { + case 1: e.x = v; break; + case 2: e.y = v; break; + case 3: e.z = v; break; + case 4: e.attr1 = v; break; + case 5: e.attr2 = v; break; + case 6: e.attr3 = v; break; + case 7: e.attr4 = v; break; + } + } + switch(e.type) + { + case LIGHT: calclight(); break; + case SOUND: audiomgr.preloadmapsound(e); break; + } + unsavededits++; + } + // give back unchanged or new entity properties + formatstring(res)("%s %d %d %d %d %d %d %d", entnames[e.type], e.x, e.y, e.z, e.attr1, e.attr2, e.attr3, e.attr4); + } + result(res); +} +COMMAND(editentity, "v"); + + void scalecomp(uchar &c, int intens) { int n = c*intens/100; diff --git a/source/src/worldio.cpp b/source/src/worldio.cpp index 6b005422..c1867c41 100644 --- a/source/src/worldio.cpp +++ b/source/src/worldio.cpp @@ -395,6 +395,13 @@ void extractconfigfile() } COMMAND(extractconfigfile, ""); +void automapconfig() +{ + conoutf("\"automatic embedded map config data\" %senabled", hdr.flags & MHF_AUTOMAPCONFIG ? "was already " : ""); + hdr.flags |= MHF_AUTOMAPCONFIG; +} +COMMAND(automapconfig, ""); + void flagmapconfigchange() { if(hdr.flags & MHF_AUTOMAPCONFIG) unsavededits++;