Track areas properly, so we don't bug out when a client has multiple cameras in different areas.
Fix up r_ignoreentpvs 0 to check areas properly. checkpvs builtin can no longer mess up area checks elsewhere. Write out foo.db files for release builds, in the hopes of at least getting function names from release-build crashes. Implement _skyroom worldspawn field, still needs a few tweaks though. Try to fix android surface-related crashes, AGAIN. Separate parsing of connect requests, in preparation for formal logins (and removal of the old ranking code). A few tweaks to try to improve compatibility with q3 mods. git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@5484 fc73d0e0-1445-4013-8a0c-d673dee63da5
This commit is contained in:
parent
6c7220324e
commit
8197e0875f
64 changed files with 1336 additions and 959 deletions
|
@ -2745,7 +2745,7 @@ static void BE_GenPolyBatches(batch_t **batches)
|
|||
}
|
||||
void R_HalfLife_GenerateBatches(entity_t *e, batch_t **batches);
|
||||
void PR_Route_Visualise(void);
|
||||
void BE_GenModelBatches(batch_t **batches, const dlight_t *dl, unsigned int bemode, qbyte *worldpvs)
|
||||
void BE_GenModelBatches(batch_t **batches, const dlight_t *dl, unsigned int bemode, const qbyte *worldpvs, const int *worldareas)
|
||||
{
|
||||
int i;
|
||||
entity_t *ent;
|
||||
|
@ -2757,7 +2757,10 @@ void BE_GenModelBatches(batch_t **batches, const dlight_t *dl, unsigned int bemo
|
|||
extern cvar_t r_ignoreentpvs; //legacy value is 1...
|
||||
|
||||
if (r_ignoreentpvs.ival)
|
||||
{
|
||||
worldpvs = NULL;
|
||||
worldareas = NULL;
|
||||
}
|
||||
|
||||
/*clear the batch list*/
|
||||
for (i = 0; i < SHADER_SORT_COUNT; i++)
|
||||
|
@ -2809,7 +2812,7 @@ void BE_GenModelBatches(batch_t **batches, const dlight_t *dl, unsigned int bemo
|
|||
}
|
||||
#endif
|
||||
|
||||
if (worldpvs && !cl.worldmodel->funcs.EdictInFatPVS(cl.worldmodel, &ent->pvscache, worldpvs))
|
||||
if (worldpvs && !cl.worldmodel->funcs.EdictInFatPVS(cl.worldmodel, &ent->pvscache, worldpvs, worldareas))
|
||||
continue;
|
||||
|
||||
switch(ent->rtype)
|
||||
|
|
|
@ -5583,12 +5583,12 @@ batch_t *GLBE_GetTempBatch(void)
|
|||
|
||||
/*called from shadowmapping code*/
|
||||
#ifdef RTLIGHTS
|
||||
void GLBE_BaseEntTextures(qbyte *worldpvs)
|
||||
void GLBE_BaseEntTextures(const qbyte *worldpvs, const int *worldareas)
|
||||
{
|
||||
batch_t *batches[SHADER_SORT_COUNT];
|
||||
batch_t **ob = shaderstate.mbatches;
|
||||
shaderstate.mbatches = batches;
|
||||
BE_GenModelBatches(batches, shaderstate.curdlight, shaderstate.mode, worldpvs);
|
||||
BE_GenModelBatches(batches, shaderstate.curdlight, shaderstate.mode, worldpvs, worldareas);
|
||||
GLBE_SubmitMeshes(NULL, SHADER_SORT_PORTAL, SHADER_SORT_SEETHROUGH+1);
|
||||
GLBE_SelectEntity(&r_worldentity);
|
||||
shaderstate.mbatches = ob;
|
||||
|
@ -6255,7 +6255,7 @@ void GLBE_DrawWorld (batch_t **worldbatches)
|
|||
}
|
||||
|
||||
//memset(batches, 0, sizeof(batches));
|
||||
BE_GenModelBatches(batches, shaderstate.curdlight, BEM_STANDARD, r_refdef.scenevis);
|
||||
BE_GenModelBatches(batches, shaderstate.curdlight, BEM_STANDARD, r_refdef.scenevis, r_refdef.sceneareas);
|
||||
R_GenDlightBatches(batches);
|
||||
shaderstate.curentity = &r_worldentity;
|
||||
// if (cl.paused || cls.state < ca_active)
|
||||
|
|
|
@ -2836,7 +2836,7 @@ void Terr_DrawInBounds(struct tdibctx *ctx, int x, int y, int w, int h)
|
|||
Terr_RebuildMesh(ctx->wmodel, s, x, y);
|
||||
}
|
||||
|
||||
if (ctx->pvs && !ctx->wmodel->funcs.EdictInFatPVS(ctx->wmodel, &s->pvscache, ctx->pvs))
|
||||
if (ctx->pvs && !ctx->wmodel->funcs.EdictInFatPVS(ctx->wmodel, &s->pvscache, ctx->pvs, NULL))
|
||||
return; //this section isn't in any visible bsp leafs
|
||||
|
||||
if (s->numents)
|
||||
|
@ -3200,7 +3200,7 @@ void Terrain_ClipDecal(fragmentdecal_t *dec, float *center, float radius, model_
|
|||
|
||||
#endif
|
||||
|
||||
unsigned int Heightmap_PointContentsHM(heightmap_t *hm, float clipmipsz, vec3_t org)
|
||||
unsigned int Heightmap_PointContentsHM(heightmap_t *hm, float clipmipsz, const vec3_t org)
|
||||
{
|
||||
float x, y;
|
||||
float z, tz;
|
||||
|
@ -3283,7 +3283,7 @@ unsigned int Heightmap_PointContentsHM(heightmap_t *hm, float clipmipsz, vec3_t
|
|||
return contents;
|
||||
}
|
||||
|
||||
unsigned int Heightmap_PointContents(model_t *model, vec3_t axis[3], vec3_t org)
|
||||
unsigned int Heightmap_PointContents(model_t *model, const vec3_t axis[3], const vec3_t org)
|
||||
{
|
||||
heightmap_t *hm = model->terrain;
|
||||
unsigned int cont;
|
||||
|
@ -3324,7 +3324,7 @@ unsigned int Heightmap_PointContents(model_t *model, vec3_t axis[3], vec3_t org)
|
|||
|
||||
return cont;
|
||||
}
|
||||
unsigned int Heightmap_NativeBoxContents(model_t *model, int hulloverride, framestate_t *framestate, vec3_t axis[3], vec3_t org, vec3_t mins, vec3_t maxs)
|
||||
unsigned int Heightmap_NativeBoxContents(model_t *model, int hulloverride, const framestate_t *framestate, const vec3_t axis[3], const vec3_t org, const vec3_t mins, const vec3_t maxs)
|
||||
{
|
||||
heightmap_t *hm = model->terrain;
|
||||
return Heightmap_PointContentsHM(hm, mins[2], org);
|
||||
|
@ -4000,7 +4000,7 @@ Why is recursion good?
|
|||
|
||||
Obviously, we don't care all that much about 1
|
||||
*/
|
||||
qboolean Heightmap_Trace(struct model_s *model, int hulloverride, framestate_t *framestate, vec3_t mataxis[3], vec3_t start, vec3_t end, vec3_t mins, vec3_t maxs, qboolean capsule, unsigned int against, struct trace_s *trace)
|
||||
qboolean Heightmap_Trace(struct model_s *model, int hulloverride, const framestate_t *framestate, const vec3_t mataxis[3], const vec3_t start, const vec3_t end, const vec3_t mins, const vec3_t maxs, qboolean capsule, unsigned int against, struct trace_s *trace)
|
||||
{
|
||||
vec2_t pos;
|
||||
vec2_t frac;
|
||||
|
@ -4271,7 +4271,7 @@ qboolean Heightmap_Trace(struct model_s *model, int hulloverride, framestate_t *
|
|||
return trace->fraction < 1;
|
||||
}
|
||||
|
||||
qboolean Heightmap_Trace_Test(struct model_s *model, int hulloverride, framestate_t *framestate, vec3_t mataxis[3], vec3_t start, vec3_t end, vec3_t mins, vec3_t maxs, qboolean capsule, unsigned int against, struct trace_s *trace)
|
||||
qboolean Heightmap_Trace_Test(struct model_s *model, int hulloverride, const framestate_t *framestate, const vec3_t mataxis[3], const vec3_t start, const vec3_t end, const vec3_t mins, const vec3_t maxs, qboolean capsule, unsigned int against, struct trace_s *trace)
|
||||
{
|
||||
qboolean ret = Heightmap_Trace(model, hulloverride, framestate, mataxis, start, end, mins, maxs, capsule, against, trace);
|
||||
|
||||
|
@ -4301,7 +4301,7 @@ typedef struct
|
|||
int id;
|
||||
int min[3], max[3];
|
||||
} hmpvsent_t;
|
||||
unsigned int Heightmap_FatPVS (model_t *mod, vec3_t org, pvsbuffer_t *pvsbuffer, qboolean add)
|
||||
unsigned int Heightmap_FatPVS (model_t *mod, const vec3_t org, pvsbuffer_t *fte_restrict pvsbuffer, qboolean add)
|
||||
{
|
||||
//embed the org onto the pvs
|
||||
hmpvs_t *hmpvs;
|
||||
|
@ -4314,12 +4314,12 @@ unsigned int Heightmap_FatPVS (model_t *mod, vec3_t org, pvsbuffer_t *pvsbuffer
|
|||
}
|
||||
|
||||
#ifndef CLIENTONLY
|
||||
qboolean Heightmap_EdictInFatPVS (model_t *mod, struct pvscache_s *edict, qbyte *pvsdata)
|
||||
qboolean Heightmap_EdictInFatPVS (model_t *mod, const struct pvscache_s *edict, const qbyte *pvsdata, const int *areas)
|
||||
{
|
||||
heightmap_t *hm = mod->terrain;
|
||||
int o[3], i;
|
||||
hmpvs_t *hmpvs = (hmpvs_t*)pvsdata;
|
||||
hmpvsent_t *hmed = (hmpvsent_t*)edict;
|
||||
const hmpvs_t *hmpvs = (const hmpvs_t*)pvsdata;
|
||||
const hmpvsent_t *hmed = (const hmpvsent_t*)edict;
|
||||
|
||||
if (!hm->culldistance)
|
||||
return true;
|
||||
|
@ -4338,7 +4338,7 @@ qboolean Heightmap_EdictInFatPVS (model_t *mod, struct pvscache_s *edict, qbyte
|
|||
return DotProduct(o,o) < hm->culldistance;
|
||||
}
|
||||
|
||||
void Heightmap_FindTouchedLeafs (model_t *mod, pvscache_t *ent, float *mins, float *maxs)
|
||||
void Heightmap_FindTouchedLeafs (model_t *mod, pvscache_t *ent, const float *mins, const float *maxs)
|
||||
{
|
||||
hmpvsent_t *hmed = (hmpvsent_t*)ent;
|
||||
|
||||
|
@ -4347,7 +4347,7 @@ void Heightmap_FindTouchedLeafs (model_t *mod, pvscache_t *ent, float *mins, flo
|
|||
}
|
||||
#endif
|
||||
|
||||
void Heightmap_LightPointValues (model_t *mod, vec3_t point, vec3_t res_diffuse, vec3_t res_ambient, vec3_t res_dir)
|
||||
void Heightmap_LightPointValues (model_t *mod, const vec3_t point, vec3_t res_diffuse, vec3_t res_ambient, vec3_t res_dir)
|
||||
{
|
||||
res_diffuse[0] = 128;
|
||||
res_diffuse[1] = 128;
|
||||
|
@ -4373,8 +4373,10 @@ qbyte *Heightmap_ClusterPVS (model_t *model, int num, pvsbuffer_t *buffer, pvsme
|
|||
// static qbyte heightmappvs = 255;
|
||||
// return &heightmappvs;
|
||||
}
|
||||
int Heightmap_ClusterForPoint (model_t *model, vec3_t point)
|
||||
int Heightmap_ClusterForPoint (model_t *model, const vec3_t point, int *area)
|
||||
{
|
||||
if (*area)
|
||||
*area = 0;
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
|
|
@ -25,8 +25,8 @@
|
|||
Nor will it work 100%
|
||||
*/
|
||||
|
||||
qboolean HLMDL_Trace (struct model_s *model, int hulloverride, framestate_t *framestate, vec3_t axis[3], vec3_t p1, vec3_t p2, vec3_t mins, vec3_t maxs, qboolean capsule, unsigned int against, struct trace_s *trace);
|
||||
unsigned int HLMDL_Contents (struct model_s *model, int hulloverride, framestate_t *framestate, vec3_t axis[3], vec3_t p, vec3_t mins, vec3_t maxs);
|
||||
qboolean HLMDL_Trace (struct model_s *model, int hulloverride, const framestate_t *framestate, const vec3_t axis[3], const vec3_t p1, const vec3_t p2, const vec3_t mins, const vec3_t maxs, qboolean capsule, unsigned int against, struct trace_s *trace);
|
||||
unsigned int HLMDL_Contents (struct model_s *model, int hulloverride, const framestate_t *framestate, const vec3_t axis[3], const vec3_t p, const vec3_t mins, const vec3_t maxs);
|
||||
|
||||
void QuaternionGLMatrix(float x, float y, float z, float w, vec4_t *GLM)
|
||||
{
|
||||
|
@ -921,7 +921,7 @@ int HLMDL_GetAttachment(model_t *mod, int tagnum, float *resultmatrix)
|
|||
return -1;
|
||||
}
|
||||
|
||||
static int HLMDL_GetBoneData_Internal(hlmodel_t *model, int firstbone, int lastbone, framestate_t *fstate, float *result)
|
||||
static int HLMDL_GetBoneData_Internal(hlmodel_t *model, int firstbone, int lastbone, const framestate_t *fstate, float *result)
|
||||
{
|
||||
int b, cbone, bgroup;
|
||||
|
||||
|
@ -939,7 +939,7 @@ static int HLMDL_GetBoneData_Internal(hlmodel_t *model, int firstbone, int lastb
|
|||
}
|
||||
return cbone;
|
||||
}
|
||||
int HLMDL_GetBoneData(model_t *mod, int firstbone, int lastbone, framestate_t *fstate, float *result)
|
||||
int HLMDL_GetBoneData(model_t *mod, int firstbone, int lastbone, const framestate_t *fstate, float *result)
|
||||
{
|
||||
return HLMDL_GetBoneData_Internal(Mod_Extradata(mod), firstbone, lastbone, fstate, result);
|
||||
}
|
||||
|
@ -966,7 +966,7 @@ qboolean HLMDL_FrameInfoForNum(model_t *mod, int surfaceidx, int seqnum, char **
|
|||
|
||||
|
||||
|
||||
qboolean HLMDL_Trace (model_t *model, int hulloverride, framestate_t *framestate, vec3_t axis[3], vec3_t p1, vec3_t p2, vec3_t mins, vec3_t maxs, qboolean capsule, unsigned int against, struct trace_s *trace)
|
||||
qboolean HLMDL_Trace (model_t *model, int hulloverride, const framestate_t *framestate, const vec3_t axis[3], const vec3_t p1, const vec3_t p2, const vec3_t mins, const vec3_t maxs, qboolean capsule, unsigned int against, struct trace_s *trace)
|
||||
{
|
||||
hlmodel_t *hm = Mod_Extradata(model);
|
||||
float *relbones;
|
||||
|
@ -1140,7 +1140,7 @@ nextbrush:
|
|||
|
||||
return trace->truefraction != 1;
|
||||
}
|
||||
unsigned int HLMDL_Contents (model_t *model, int hulloverride, framestate_t *framestate, vec3_t axis[3], vec3_t p, vec3_t mins, vec3_t maxs)
|
||||
unsigned int HLMDL_Contents (model_t *model, int hulloverride, const framestate_t *framestate, const vec3_t axis[3], const vec3_t p, const vec3_t mins, const vec3_t maxs)
|
||||
{
|
||||
trace_t tr;
|
||||
HLMDL_Trace(model, hulloverride, framestate, axis, p, p, mins, maxs, false, ~0, &tr);
|
||||
|
|
|
@ -230,42 +230,35 @@ typedef struct
|
|||
qbyte *buffer; //reallocated if needed.
|
||||
size_t buffersize;
|
||||
} pvsbuffer_t;
|
||||
#if 1
|
||||
typedef char *pvsmerge_t;
|
||||
#define PVM_FAST ((char*)0)
|
||||
#define PVM_MERGE ((char*)1)
|
||||
#define PVM_REPLACE ((char*)2)
|
||||
#else
|
||||
typedef enum
|
||||
{
|
||||
PVM_FAST,
|
||||
PVM_MERGE, //merge the pvs bits into the provided buffer
|
||||
PVM_REPLACE,//return value is guarenteed to be the provided buffer.
|
||||
} pvsmerge_t;
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
//model is being purged from memory.
|
||||
void (*PurgeModel) (struct model_s *mod);
|
||||
|
||||
unsigned int (*PointContents) (struct model_s *model, vec3_t axis[3], vec3_t p);
|
||||
unsigned int (*BoxContents) (struct model_s *model, int hulloverride, framestate_t *framestate, vec3_t axis[3], vec3_t p, vec3_t mins, vec3_t maxs);
|
||||
unsigned int (*PointContents) (struct model_s *model, const vec3_t axis[3], const vec3_t p);
|
||||
unsigned int (*BoxContents) (struct model_s *model, int hulloverride, const framestate_t *framestate, const vec3_t axis[3], const vec3_t p, const vec3_t mins, const vec3_t maxs);
|
||||
|
||||
//deals with whatever is native for the bsp (gamecode is expected to distinguish this).
|
||||
qboolean (*NativeTrace) (struct model_s *model, int hulloverride, framestate_t *framestate, vec3_t axis[3], vec3_t p1, vec3_t p2, vec3_t mins, vec3_t maxs, qboolean capsule, unsigned int against, struct trace_s *trace);
|
||||
unsigned int (*NativeContents)(struct model_s *model, int hulloverride, framestate_t *framestate, vec3_t axis[3], vec3_t p, vec3_t mins, vec3_t maxs);
|
||||
qboolean (*NativeTrace) (struct model_s *model, int hulloverride, const framestate_t *framestate, const vec3_t axis[3], const vec3_t p1, const vec3_t p2, const vec3_t mins, const vec3_t maxs, qboolean capsule, unsigned int against, struct trace_s *trace);
|
||||
unsigned int (*NativeContents)(struct model_s *model, int hulloverride, const framestate_t *framestate, const vec3_t axis[3], const vec3_t p, const vec3_t mins, const vec3_t maxs);
|
||||
|
||||
unsigned int (*FatPVS) (struct model_s *model, vec3_t org, pvsbuffer_t *pvsbuffer, qboolean merge);
|
||||
qboolean (*EdictInFatPVS) (struct model_s *model, struct pvscache_s *edict, qbyte *pvs);
|
||||
void (*FindTouchedLeafs) (struct model_s *model, struct pvscache_s *ent, vec3_t cullmins, vec3_t cullmaxs); //edict system as opposed to q2 game dll system.
|
||||
unsigned int (*FatPVS) (struct model_s *model, const vec3_t org, pvsbuffer_t *pvsbuffer, qboolean merge);
|
||||
qboolean (*EdictInFatPVS) (struct model_s *model, const struct pvscache_s *edict, const qbyte *pvs, const int *areas); //areas[0] is the count of accepted areas, if valid.
|
||||
void (*FindTouchedLeafs) (struct model_s *model, struct pvscache_s *ent, const vec3_t cullmins, const vec3_t cullmaxs); //edict system as opposed to q2 game dll system.
|
||||
|
||||
void (*LightPointValues) (struct model_s *model, vec3_t point, vec3_t res_diffuse, vec3_t res_ambient, vec3_t res_dir);
|
||||
void (*LightPointValues) (struct model_s *model, const vec3_t point, vec3_t res_diffuse, vec3_t res_ambient, vec3_t res_dir);
|
||||
void (*StainNode) (struct mnode_s *node, float *parms);
|
||||
void (*MarkLights) (struct dlight_s *light, int bit, struct mnode_s *node);
|
||||
|
||||
int (*ClusterForPoint) (struct model_s *model, vec3_t point); //pvs index (leaf-1 for q1bsp). may be negative (ie: no pvs).
|
||||
int (*ClusterForPoint) (struct model_s *model, const vec3_t point, int *areaout); //pvs index (leaf-1 for q1bsp). may be negative (ie: no pvs).
|
||||
qbyte *(*ClusterPVS) (struct model_s *model, int cluster, pvsbuffer_t *pvsbuffer, pvsmerge_t merge);
|
||||
qbyte *(*ClustersInSphere) (struct model_s *model, vec3_t point, float radius, pvsbuffer_t *pvsbuffer, qbyte *unionwith);
|
||||
qbyte *(*ClustersInSphere) (struct model_s *model, const vec3_t point, float radius, pvsbuffer_t *pvsbuffer, const qbyte *fte_restrict unionwith);
|
||||
} modelfuncs_t;
|
||||
|
||||
|
||||
|
@ -571,8 +564,8 @@ size_t Fragment_ClipPlaneToBrush(vecV_t *points, size_t maxpoints, void *planes,
|
|||
void Mod_ClipDecal(struct model_s *mod, vec3_t center, vec3_t normal, vec3_t tangent1, vec3_t tangent2, float size, unsigned int surfflagmask, unsigned int surflagmatch, void (*callback)(void *ctx, vec3_t *fte_restrict points, size_t numpoints, shader_t *shader), void *ctx);
|
||||
|
||||
void Q1BSP_MarkLights (dlight_t *light, int bit, mnode_t *node);
|
||||
void GLQ1BSP_LightPointValues(struct model_s *model, vec3_t point, vec3_t res_diffuse, vec3_t res_ambient, vec3_t res_dir);
|
||||
qboolean Q1BSP_RecursiveHullCheck (hull_t *hull, int num, vec3_t p1, vec3_t p2, unsigned int hitcontents, struct trace_s *trace);
|
||||
void GLQ1BSP_LightPointValues(struct model_s *model, const vec3_t point, vec3_t res_diffuse, vec3_t res_ambient, vec3_t res_dir);
|
||||
qboolean Q1BSP_RecursiveHullCheck (hull_t *hull, int num, const vec3_t p1, const vec3_t p2, unsigned int hitcontents, struct trace_s *trace);
|
||||
|
||||
/*
|
||||
==============================================================================
|
||||
|
@ -1077,7 +1070,7 @@ typedef struct model_s
|
|||
#endif // __MODEL__
|
||||
|
||||
|
||||
float RadiusFromBounds (vec3_t mins, vec3_t maxs);
|
||||
float RadiusFromBounds (const vec3_t mins, const vec3_t maxs);
|
||||
|
||||
|
||||
//
|
||||
|
@ -1093,8 +1086,8 @@ void Terr_FinishTerrain(model_t *model);
|
|||
void Terr_PurgeTerrainModel(model_t *hm, qboolean lightmapsonly, qboolean lightmapreusable);
|
||||
void *Mod_LoadTerrainInfo(model_t *mod, char *loadname, qboolean force); //call this after loading a bsp
|
||||
qboolean Terrain_LocateSection(const char *name, flocation_t *loc); //used on servers to generate sections for download.
|
||||
qboolean Heightmap_Trace(model_t *model, int forcehullnum, framestate_t *framestate, vec3_t axis[3], vec3_t start, vec3_t end, vec3_t mins, vec3_t maxs, qboolean capsule, unsigned int contentmask, struct trace_s *trace);
|
||||
unsigned int Heightmap_PointContents(model_t *model, vec3_t axis[3], vec3_t org);
|
||||
qboolean Heightmap_Trace(model_t *model, int forcehullnum, const framestate_t *framestate, const vec3_t axis[3], const vec3_t start, const vec3_t end, const vec3_t mins, const vec3_t maxs, qboolean capsule, unsigned int contentmask, struct trace_s *trace);
|
||||
unsigned int Heightmap_PointContents(model_t *model, const vec3_t axis[3], const vec3_t org);
|
||||
struct fragmentdecal_s;
|
||||
void Terrain_ClipDecal(struct fragmentdecal_s *dec, float *center, float radius, model_t *model);
|
||||
qboolean Terr_DownloadedSection(char *fname);
|
||||
|
@ -1122,22 +1115,22 @@ void CM_InitBoxHull (void);
|
|||
void CM_Init(void);
|
||||
|
||||
qboolean CM_SetAreaPortalState (struct model_s *mod, int portalnum, qboolean open);
|
||||
qboolean CM_HeadnodeVisible (struct model_s *mod, int nodenum, qbyte *visbits);
|
||||
qboolean CM_HeadnodeVisible (struct model_s *mod, int nodenum, const qbyte *visbits);
|
||||
qboolean VARGS CM_AreasConnected (struct model_s *mod, unsigned int area1, unsigned int area2);
|
||||
int CM_ClusterBytes (struct model_s *mod);
|
||||
int CM_LeafContents (struct model_s *mod, int leafnum);
|
||||
int CM_LeafCluster (struct model_s *mod, int leafnum);
|
||||
int CM_LeafArea (struct model_s *mod, int leafnum);
|
||||
int CM_WriteAreaBits (struct model_s *mod, qbyte *buffer, int area, qboolean merge);
|
||||
int CM_PointLeafnum (struct model_s *mod, vec3_t p);
|
||||
int CM_PointLeafnum (struct model_s *mod, const vec3_t p);
|
||||
qbyte *CM_ClusterPVS (struct model_s *mod, int cluster, pvsbuffer_t *buffer, pvsmerge_t merge);
|
||||
qbyte *CM_ClusterPHS (struct model_s *mod, int cluster, pvsbuffer_t *buffer);
|
||||
int CM_BoxLeafnums (struct model_s *mod, vec3_t mins, vec3_t maxs, int *list, int listsize, int *topnode);
|
||||
int CM_PointContents (struct model_s *mod, vec3_t p);
|
||||
int CM_TransformedPointContents (struct model_s *mod, vec3_t p, int headnode, vec3_t origin, vec3_t angles);
|
||||
int CM_HeadnodeForBox (struct model_s *mod, vec3_t mins, vec3_t maxs);
|
||||
int CM_BoxLeafnums (struct model_s *mod, const vec3_t mins, const vec3_t maxs, int *list, int listsize, int *topnode);
|
||||
int CM_PointContents (struct model_s *mod, const vec3_t p);
|
||||
int CM_TransformedPointContents (struct model_s *mod, const vec3_t p, int headnode, const vec3_t origin, const vec3_t angles);
|
||||
int CM_HeadnodeForBox (struct model_s *mod, const vec3_t mins, const vec3_t maxs);
|
||||
//struct trace_s CM_TransformedBoxTrace (struct model_s *mod, vec3_t start, vec3_t end, vec3_t mins, vec3_t maxs, int brushmask, vec3_t origin, vec3_t angles);
|
||||
struct model_s *CM_TempBoxModel(vec3_t mins, vec3_t maxs);
|
||||
struct model_s *CM_TempBoxModel(const vec3_t mins, const vec3_t maxs);
|
||||
|
||||
//for gamecode to control portals/areas
|
||||
void CMQ2_SetAreaPortalState (model_t *mod, unsigned int portalnum, qboolean open);
|
||||
|
|
|
@ -2301,7 +2301,7 @@ LIGHT SAMPLING
|
|||
mplane_t *lightplane;
|
||||
vec3_t lightspot;
|
||||
|
||||
static void GLQ3_AddLatLong(qbyte latlong[2], vec3_t dir, float mag)
|
||||
static void GLQ3_AddLatLong(const qbyte latlong[2], vec3_t dir, float mag)
|
||||
{
|
||||
float lat = (float)latlong[0] * (2 * M_PI)*(1.0 / 255.0);
|
||||
float lng = (float)latlong[1] * (2 * M_PI)*(1.0 / 255.0);
|
||||
|
@ -2310,7 +2310,7 @@ static void GLQ3_AddLatLong(qbyte latlong[2], vec3_t dir, float mag)
|
|||
dir[2] += mag * cos ( lat );
|
||||
}
|
||||
|
||||
void GLQ3_LightGrid(model_t *mod, vec3_t point, vec3_t res_diffuse, vec3_t res_ambient, vec3_t res_dir)
|
||||
void GLQ3_LightGrid(model_t *mod, const vec3_t point, vec3_t res_diffuse, vec3_t res_ambient, vec3_t res_dir)
|
||||
{
|
||||
q3lightgridinfo_t *lg = (q3lightgridinfo_t *)cl.worldmodel->lightgrid;
|
||||
int index[8];
|
||||
|
@ -2585,7 +2585,7 @@ int R_LightPoint (vec3_t p)
|
|||
|
||||
#ifdef PEXT_LIGHTSTYLECOL
|
||||
|
||||
static float *GLRecursiveLightPoint3C (model_t *mod, mnode_t *node, vec3_t start, vec3_t end)
|
||||
static float *GLRecursiveLightPoint3C (model_t *mod, mnode_t *node, const vec3_t start, const vec3_t end)
|
||||
{
|
||||
static float l[6];
|
||||
float *r;
|
||||
|
@ -2813,7 +2813,7 @@ static float *GLRecursiveLightPoint3C (model_t *mod, mnode_t *node, vec3_t start
|
|||
|
||||
#endif
|
||||
|
||||
void GLQ1BSP_LightPointValues(model_t *model, vec3_t point, vec3_t res_diffuse, vec3_t res_ambient, vec3_t res_dir)
|
||||
void GLQ1BSP_LightPointValues(model_t *model, const vec3_t point, vec3_t res_diffuse, vec3_t res_ambient, vec3_t res_dir)
|
||||
{
|
||||
vec3_t end;
|
||||
float *r;
|
||||
|
|
|
@ -1068,7 +1068,7 @@ void GLR_DrawPortal(batch_t *batch, batch_t **blist, batch_t *depthmasklist[2],
|
|||
d += 0.1; //an epsilon on the far side
|
||||
VectorMA(point, d, plane.normal, point);
|
||||
|
||||
clust = cl.worldmodel->funcs.ClusterForPoint(cl.worldmodel, point);
|
||||
clust = cl.worldmodel->funcs.ClusterForPoint(cl.worldmodel, point, NULL);
|
||||
if (i == batch->firstmesh)
|
||||
r_refdef.forcedvis = cl.worldmodel->funcs.ClusterPVS(cl.worldmodel, clust, &newvis, PVM_REPLACE);
|
||||
else
|
||||
|
@ -1145,7 +1145,6 @@ void GLR_DrawPortal(batch_t *batch, batch_t **blist, batch_t *depthmasklist[2],
|
|||
{ //q3-style portal, where a single entity provides orientation+two origins
|
||||
float d;
|
||||
vec3_t paxis[3], porigin, vaxis[3], vorg;
|
||||
void PerpendicularVector( vec3_t dst, const vec3_t src );
|
||||
|
||||
oplane = plane;
|
||||
|
||||
|
|
|
@ -3174,7 +3174,11 @@ static void Shaderpass_RGBGen (parsestate_t *ps, char **ptr)
|
|||
else if (!Q_stricmp (token, "oneMinusEntity"))
|
||||
pass->rgbgen = RGB_GEN_ONE_MINUS_ENTITY;
|
||||
else if (!Q_stricmp (token, "vertex"))
|
||||
{
|
||||
pass->rgbgen = RGB_GEN_VERTEX_LIGHTING;
|
||||
if (pass->alphagen == ALPHA_GEN_UNDEFINED) //matches Q3, and is a perf gain, even if its inconsistent.
|
||||
pass->alphagen = ALPHA_GEN_VERTEX;
|
||||
}
|
||||
else if (!Q_stricmp (token, "oneMinusVertex"))
|
||||
pass->rgbgen = RGB_GEN_ONE_MINUS_VERTEX;
|
||||
else if (!Q_stricmp (token, "lightingDiffuse"))
|
||||
|
@ -4524,7 +4528,7 @@ void Shader_Readpass (parsestate_t *ps)
|
|||
pass->anim_frames[0] = r_nulltex;
|
||||
pass->anim_numframes = 0;
|
||||
pass->rgbgen = RGB_GEN_UNKNOWN;
|
||||
pass->alphagen = ALPHA_GEN_IDENTITY;
|
||||
pass->alphagen = ALPHA_GEN_UNDEFINED;
|
||||
pass->tcgen = TC_GEN_UNSPECIFIED;
|
||||
pass->numtcmods = 0;
|
||||
pass->stagetype = ST_AMBIENT;
|
||||
|
@ -4554,6 +4558,9 @@ void Shader_Readpass (parsestate_t *ps)
|
|||
}
|
||||
}
|
||||
|
||||
if (pass->alphagen == ALPHA_GEN_UNDEFINED)
|
||||
pass->alphagen = ALPHA_GEN_IDENTITY;
|
||||
|
||||
//if there was no texgen, then its too late now.
|
||||
if (!pass->numMergedPasses)
|
||||
pass->numMergedPasses = 1;
|
||||
|
|
|
@ -1578,7 +1578,7 @@ static struct shadowmesh_s *SHM_BuildShadowMesh(dlight_t *dl, unsigned char *lvi
|
|||
lvis = cl.worldmodel->funcs.ClustersInSphere(cl.worldmodel, dl->origin, dl->radius, &lvisb, NULL);
|
||||
else
|
||||
{
|
||||
clus = cl.worldmodel->funcs.ClusterForPoint(cl.worldmodel, dl->origin);
|
||||
clus = cl.worldmodel->funcs.ClusterForPoint(cl.worldmodel, dl->origin, NULL); //FIXME: track the lights area
|
||||
lvis = cl.worldmodel->funcs.ClusterPVS(cl.worldmodel, clus, &lvisb, PVM_FAST);
|
||||
|
||||
if (cl.worldmodel->funcs.ClustersInSphere)
|
||||
|
@ -1649,7 +1649,7 @@ static struct shadowmesh_s *SHM_BuildShadowMesh(dlight_t *dl, unsigned char *lvi
|
|||
sh_shadowframe++;
|
||||
|
||||
{
|
||||
int cluster = cl.worldmodel->funcs.ClusterForPoint(cl.worldmodel, dl->origin);
|
||||
int cluster = cl.worldmodel->funcs.ClusterForPoint(cl.worldmodel, dl->origin, NULL);
|
||||
if (cluster >= 0)
|
||||
sh_shmesh->litleaves[cluster>>3] |= 1<<(cluster&7);
|
||||
}
|
||||
|
@ -2237,7 +2237,7 @@ static void Sh_LightFrustumPlanes(dlight_t *l, vec3_t axis[3], vec4_t *planes, i
|
|||
|
||||
//culling for the face happens in the caller.
|
||||
//these faces should thus match Sh_LightFrustumPlanes
|
||||
static void Sh_GenShadowFace(dlight_t *l, vec3_t axis[3], int lighttype, shadowmesh_t *smesh, int face, int smsize, float proj[16], qbyte *lightpvs)
|
||||
static void Sh_GenShadowFace(dlight_t *l, vec3_t axis[3], int lighttype, shadowmesh_t *smesh, int face, int smsize, float proj[16], const qbyte *lightpvs)
|
||||
{
|
||||
vec3_t t1,t2,t3;
|
||||
texture_t *tex;
|
||||
|
@ -2379,7 +2379,7 @@ static void Sh_GenShadowFace(dlight_t *l, vec3_t axis[3], int lighttype, shadowm
|
|||
break;
|
||||
#ifdef GLQUAKE
|
||||
case QR_OPENGL:
|
||||
GLBE_BaseEntTextures(lightpvs);
|
||||
GLBE_BaseEntTextures(lightpvs, NULL);
|
||||
|
||||
if (lighttype & LSHADER_ORTHO)
|
||||
qglDisable(GL_DEPTH_CLAMP_ARB);
|
||||
|
@ -2387,17 +2387,17 @@ static void Sh_GenShadowFace(dlight_t *l, vec3_t axis[3], int lighttype, shadowm
|
|||
#endif
|
||||
#ifdef D3D9QUAKE
|
||||
case QR_DIRECT3D9:
|
||||
D3D9BE_BaseEntTextures(lightpvs);
|
||||
D3D9BE_BaseEntTextures(lightpvs, NULL);
|
||||
break;
|
||||
#endif
|
||||
#ifdef D3D11QUAKE
|
||||
case QR_DIRECT3D11:
|
||||
D3D11BE_BaseEntTextures(lightpvs);
|
||||
D3D11BE_BaseEntTextures(lightpvs, NULL);
|
||||
break;
|
||||
#endif
|
||||
#ifdef VKQUAKE
|
||||
case QR_VULKAN:
|
||||
VKBE_BaseEntTextures(lightpvs);
|
||||
VKBE_BaseEntTextures(lightpvs, NULL);
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
|
@ -2616,7 +2616,7 @@ qboolean Sh_GenerateShadowMap(dlight_t *l, int lighttype)
|
|||
else
|
||||
{
|
||||
int clus;
|
||||
clus = cl.worldmodel->funcs.ClusterForPoint(cl.worldmodel, l->origin);
|
||||
clus = cl.worldmodel->funcs.ClusterForPoint(cl.worldmodel, l->origin, NULL);
|
||||
lvis = cl.worldmodel->funcs.ClusterPVS(cl.worldmodel, clus, &lvisb, PVM_FAST);
|
||||
//FIXME: surely we can use the phs for this?
|
||||
|
||||
|
@ -2732,12 +2732,12 @@ static void Sh_DrawShadowMapLight(dlight_t *l, vec3_t colour, vec3_t axis[3], qb
|
|||
else
|
||||
{
|
||||
int clus;
|
||||
clus = cl.worldmodel->funcs.ClusterForPoint(cl.worldmodel, l->origin);
|
||||
clus = cl.worldmodel->funcs.ClusterForPoint(cl.worldmodel, l->origin, NULL);
|
||||
lvis = cl.worldmodel->funcs.ClusterPVS(cl.worldmodel, clus, &lvisb, PVM_FAST);
|
||||
//FIXME: surely we can use the phs for this?
|
||||
if (cl.worldmodel->funcs.ClustersInSphere)
|
||||
lvis = cl.worldmodel->funcs.ClustersInSphere(cl.worldmodel, l->origin, l->radius, &lvisb2, lvis);
|
||||
|
||||
//FIXME: check areas
|
||||
if (!Sh_VisOverlaps(lvis, vvis)) //The two viewing areas do not intersect.
|
||||
{
|
||||
RQuantAdd(RQUANT_RTLIGHT_CULL_PVS, 1);
|
||||
|
@ -2856,22 +2856,22 @@ static void Sh_DrawEntLighting(dlight_t *light, vec3_t colour, qbyte *pvs)
|
|||
break;
|
||||
#ifdef GLQUAKE
|
||||
case QR_OPENGL:
|
||||
GLBE_BaseEntTextures(pvs);
|
||||
GLBE_BaseEntTextures(pvs, NULL);
|
||||
break;
|
||||
#endif
|
||||
#ifdef VKQUAKE
|
||||
case QR_VULKAN:
|
||||
VKBE_BaseEntTextures(pvs);
|
||||
VKBE_BaseEntTextures(pvs, NULL);
|
||||
break;
|
||||
#endif
|
||||
#ifdef D3D9QUAKE
|
||||
case QR_DIRECT3D9:
|
||||
D3D9BE_BaseEntTextures(pvs);
|
||||
D3D9BE_BaseEntTextures(pvs, NULL);
|
||||
break;
|
||||
#endif
|
||||
#ifdef D3D11QUAKE
|
||||
case QR_DIRECT3D11:
|
||||
D3D11BE_BaseEntTextures(pvs);
|
||||
D3D11BE_BaseEntTextures(pvs, NULL);
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
|
@ -3159,7 +3159,7 @@ static qboolean Sh_DrawStencilLight(dlight_t *dl, vec3_t colour, vec3_t axis[3],
|
|||
}
|
||||
else
|
||||
{
|
||||
clus = cl.worldmodel->funcs.ClusterForPoint(cl.worldmodel, dl->origin);
|
||||
clus = cl.worldmodel->funcs.ClusterForPoint(cl.worldmodel, dl->origin, NULL); //FIXME: check areas
|
||||
lvis = cl.worldmodel->funcs.ClusterPVS(cl.worldmodel, clus, &lvisb, PVM_FAST);
|
||||
// if (cl.worldmodel->funcs.ClustersInSphere)
|
||||
// lvis = cl.worldmodel->funcs.ClustersInSphere(cl.worldmodel, dl->origin, dl->radius, &lvisb2, lvis);
|
||||
|
@ -3388,7 +3388,7 @@ qboolean Sh_CullLight(dlight_t *dl, qbyte *vvis)
|
|||
int clus;
|
||||
qbyte *lvis;
|
||||
|
||||
clus = cl.worldmodel->funcs.ClusterForPoint(cl.worldmodel, dl->origin);
|
||||
clus = cl.worldmodel->funcs.ClusterForPoint(cl.worldmodel, dl->origin, NULL);
|
||||
lvis = cl.worldmodel->funcs.ClusterPVS(cl.worldmodel, clus, &lvisb, PVM_FAST);
|
||||
// if (cl.worldmodel->funcs.ClustersInSphere)
|
||||
// lvis = cl.worldmodel->funcs.ClustersInSphere(cl.worldmodel, dl->origin, dl->radius, &lvisb2, lvis);
|
||||
|
@ -3433,12 +3433,12 @@ static void Sh_DrawShadowlessLight(dlight_t *dl, vec3_t colour, vec3_t axis[3],
|
|||
lvis = cl.worldmodel->funcs.ClustersInSphere(cl.worldmodel, dl->origin, dl->radius, &lvisb2, NULL);
|
||||
else
|
||||
{
|
||||
clus = cl.worldmodel->funcs.ClusterForPoint(cl.worldmodel, dl->origin);
|
||||
clus = cl.worldmodel->funcs.ClusterForPoint(cl.worldmodel, dl->origin, NULL);
|
||||
lvis = cl.worldmodel->funcs.ClusterPVS(cl.worldmodel, clus, &lvisb, PVM_FAST);
|
||||
}
|
||||
|
||||
SHM_BuildShadowMesh(dl, lvis, SMT_SHADOWLESS);
|
||||
|
||||
//FIXME: check areas
|
||||
if (!Sh_VisOverlaps(lvis, vvis)) //The two viewing areas do not intersect.
|
||||
{
|
||||
RQuantAdd(RQUANT_RTLIGHT_CULL_PVS, 1);
|
||||
|
@ -3645,7 +3645,7 @@ void Sh_PreGenerateLights(void)
|
|||
lvis = cl.worldmodel->funcs.ClustersInSphere(cl.worldmodel, dl->origin, dl->radius, &lvisb2, NULL);
|
||||
else
|
||||
{ //other lights only want to use the source leaf's pvs (clamped by the sphere)
|
||||
leaf = cl.worldmodel->funcs.ClusterForPoint(cl.worldmodel, dl->origin);
|
||||
leaf = cl.worldmodel->funcs.ClusterForPoint(cl.worldmodel, dl->origin, NULL);
|
||||
lvis = cl.worldmodel->funcs.ClusterPVS(cl.worldmodel, leaf, &lvisb, PVM_FAST);
|
||||
if (cl.worldmodel->funcs.ClustersInSphere)
|
||||
lvis = cl.worldmodel->funcs.ClustersInSphere(cl.worldmodel, dl->origin, dl->radius, &lvisb2, lvis);
|
||||
|
|
|
@ -131,24 +131,12 @@ qboolean R_DrawSkyroom(shader_t *skyshader)
|
|||
if (!r_refdef.skyroom_enabled || r_refdef.recurse >= R_MAX_RECURSE-1)
|
||||
return false;
|
||||
|
||||
if (skyshader->numpasses)
|
||||
{
|
||||
shaderpass_t *pass = skyshader->passes;
|
||||
if (pass->shaderbits & SBITS_ATEST_BITS) //alphatests
|
||||
;
|
||||
else if (pass->shaderbits & SBITS_MASK_BITS) //colormasks
|
||||
;
|
||||
else if ((pass->shaderbits & SBITS_BLEND_BITS) != 0 && (pass->shaderbits & SBITS_BLEND_BITS) != (SBITS_SRCBLEND_ONE|SBITS_DSTBLEND_ZERO)) //blendfunc
|
||||
;
|
||||
else
|
||||
return false; //that shader looks like its opaque.
|
||||
}
|
||||
|
||||
oldrefdef = r_refdef;
|
||||
r_refdef.recurse+=1;
|
||||
|
||||
r_refdef.externalview = true;
|
||||
r_refdef.skyroom_enabled = false;
|
||||
r_refdef.flags |= RDF_DISABLEPARTICLES;
|
||||
|
||||
/*work out where the camera should be (use the same angles)*/
|
||||
VectorCopy(r_refdef.skyroom_pos, r_refdef.vieworg);
|
||||
|
@ -231,8 +219,21 @@ qboolean R_DrawSkyChain (batch_t *batch)
|
|||
skyboxtex = NULL;
|
||||
|
||||
if (R_DrawSkyroom(skyshader))
|
||||
{
|
||||
{ //don't obscure the skyroom if the sky shader is opaque.
|
||||
qboolean opaque = false;
|
||||
if (skyshader->numpasses)
|
||||
{
|
||||
shaderpass_t *pass = skyshader->passes;
|
||||
if (pass->shaderbits & SBITS_ATEST_BITS) //alphatests
|
||||
;
|
||||
else if (pass->shaderbits & SBITS_MASK_BITS) //colormasks
|
||||
;
|
||||
else if ((pass->shaderbits & SBITS_BLEND_BITS) != 0 && (pass->shaderbits & SBITS_BLEND_BITS) != (SBITS_SRCBLEND_ONE|SBITS_DSTBLEND_ZERO)) //blendfunc
|
||||
;
|
||||
else
|
||||
opaque = true; //that shader looks like its opaque.
|
||||
}
|
||||
if (!opaque)
|
||||
GL_DrawSkySphere(batch, skyshader);
|
||||
}
|
||||
else if (skyboxtex && TEXVALID(*skyboxtex))
|
||||
|
|
|
@ -318,6 +318,7 @@ extern vec3_t r_origin;
|
|||
//
|
||||
extern refdef_t r_refdef;
|
||||
extern unsigned int r_viewcontents;
|
||||
int r_viewarea;
|
||||
extern int r_viewcluster, r_viewcluster2, r_oldviewcluster, r_oldviewcluster2; //q2
|
||||
extern texture_t *r_notexture_mip;
|
||||
extern int d_lightstylevalue[256]; // 8.8 fraction of base light value
|
||||
|
@ -406,7 +407,7 @@ void R_InitFlashblends(void);
|
|||
#ifdef GLQUAKE
|
||||
void GLR_MarkQ2Lights (dlight_t *light, int bit, mnode_t *node);
|
||||
#endif
|
||||
void GLQ3_LightGrid(model_t *mod, vec3_t point, vec3_t res_diffuse, vec3_t res_ambient, vec3_t res_dir);
|
||||
void GLQ3_LightGrid(model_t *mod, const vec3_t point, vec3_t res_diffuse, vec3_t res_ambient, vec3_t res_dir);
|
||||
qboolean R_LoadRTLights(void);
|
||||
qboolean R_ImportRTLights(const char *entlump);
|
||||
|
||||
|
|
|
@ -336,7 +336,7 @@ qboolean HLMDL_GetModelEvent(model_t *model, int animation, int eventidx, float
|
|||
int HLMDL_GetNumBones(model_t *mod, qboolean tagstoo);
|
||||
int HLMDL_GetBoneParent(model_t *mod, int bonenum);
|
||||
const char *HLMDL_GetBoneName(model_t *mod, int bonenum);
|
||||
int HLMDL_GetBoneData(model_t *model, int firstbone, int lastbone, framestate_t *fstate, float *result);
|
||||
int HLMDL_GetBoneData(model_t *model, int firstbone, int lastbone, const framestate_t *fstate, float *result);
|
||||
int HLMDL_GetAttachment(model_t *model, int tagnum, float *resultmatrix);
|
||||
|
||||
#ifndef SERVERONLY
|
||||
|
|
|
@ -217,6 +217,7 @@ typedef struct shaderpass_s {
|
|||
shaderfunc_t rgbgen_func;
|
||||
|
||||
enum {
|
||||
ALPHA_GEN_UNDEFINED,
|
||||
ALPHA_GEN_ENTITY,
|
||||
ALPHA_GEN_WAVE,
|
||||
ALPHA_GEN_PORTAL,
|
||||
|
@ -990,9 +991,9 @@ void GLBE_PolyOffsetStencilShadow(qboolean foobar);
|
|||
void GLBE_PolyOffsetStencilShadow(void);
|
||||
#endif
|
||||
//Called from shadowmapping code into backend
|
||||
void GLBE_BaseEntTextures(qbyte *worldpvs);
|
||||
void D3D9BE_BaseEntTextures(qbyte *worldpvs);
|
||||
void D3D11BE_BaseEntTextures(qbyte *worldpvs);
|
||||
void GLBE_BaseEntTextures(const qbyte *worldpvs, const int *worldareas);
|
||||
void D3D9BE_BaseEntTextures(const qbyte *worldpvs, const int *worldareas);
|
||||
void D3D11BE_BaseEntTextures(const qbyte *worldpvs, const int *worldareas);
|
||||
//prebuilds shadow volumes
|
||||
void Sh_PreGenerateLights(void);
|
||||
//Draws lights, called from the backend
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue