Stub some wrath builtins mostly for docs.
This commit is contained in:
parent
8b0cfff7c7
commit
92fd7eb9a7
11 changed files with 383 additions and 235 deletions
|
@ -394,7 +394,7 @@ static void J_JoystickButton(SDL_JoystickID jid, int button, qboolean pressed)
|
|||
}
|
||||
}
|
||||
|
||||
int INS_GetControllerType(int id)
|
||||
enum controllertype_e INS_GetControllerType(int id)
|
||||
{
|
||||
#if SDL_VERSION_ATLEAST(2,0,12)
|
||||
int i;
|
||||
|
@ -405,27 +405,35 @@ int INS_GetControllerType(int id)
|
|||
switch(SDL_GameControllerTypeForIndex(sdljoy[i].id))
|
||||
{
|
||||
default: //for the future...
|
||||
case SDL_CONTROLLER_TYPE_UNKNOWN:
|
||||
return CONTROLLER_UNKNOWN;
|
||||
#if SDL_VERSION_ATLEAST(2,0,14)
|
||||
case SDL_CONTROLLER_TYPE_VIRTUAL: //don't really know... assume steaminput and thus steamdeck and thus xbox-like.
|
||||
return CONTROLLER_VIRTUAL;
|
||||
#endif
|
||||
return 1;
|
||||
case SDL_CONTROLLER_TYPE_UNKNOWN:
|
||||
return 0;
|
||||
case SDL_CONTROLLER_TYPE_XBOX360:
|
||||
case SDL_CONTROLLER_TYPE_XBOXONE:
|
||||
#if SDL_VERSION_ATLEAST(2,0,16)
|
||||
case SDL_CONTROLLER_TYPE_GOOGLE_STADIA: //close enough
|
||||
case SDL_CONTROLLER_TYPE_AMAZON_LUNA: //it'll do. I guess we're starting to see a standard here.
|
||||
#endif
|
||||
return 1; //a on bottom, b('cancel') to right
|
||||
#if SDL_VERSION_ATLEAST(2,0,24)
|
||||
case SDL_CONTROLLER_TYPE_NVIDIA_SHIELD:
|
||||
#endif
|
||||
return CONTROLLER_XBOX; //a on bottom, b('cancel') to right
|
||||
case SDL_CONTROLLER_TYPE_PS3:
|
||||
case SDL_CONTROLLER_TYPE_PS4:
|
||||
#if SDL_VERSION_ATLEAST(2,0,14)
|
||||
case SDL_CONTROLLER_TYPE_PS5:
|
||||
#endif
|
||||
return 2; //weird indecipherable shapes.
|
||||
return CONTROLLER_PLAYSTATION; //weird indecipherable shapes.
|
||||
case SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO:
|
||||
return 3; //b on bottom, a('cancel') to right
|
||||
#if SDL_VERSION_ATLEAST(2,0,24)
|
||||
case SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_LEFT:
|
||||
case SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT:
|
||||
case SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_PAIR:
|
||||
#endif
|
||||
return CONTROLLER_NINTENDO; //b on bottom, a('cancel') to right
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -510,6 +510,23 @@ static int Joy_AllocateDevID(void)
|
|||
}
|
||||
}
|
||||
|
||||
enum controllertype_e INS_GetControllerType(int id)
|
||||
{
|
||||
int j;
|
||||
for (j = 0; j < joy_count; j++)
|
||||
{
|
||||
if (wjoy[j].devid == id)
|
||||
{
|
||||
if (wjoy[j].devstate == DS_NOTPRESENT)
|
||||
return CONTROLLER_NONE;
|
||||
if (wjoy[j].isxinput)
|
||||
return CONTROLLER_XBOX; //we don't really know, but we're using it through an xbox-specific api, so...
|
||||
return CONTROLLER_UNKNOWN; //the legacy joy API.
|
||||
}
|
||||
}
|
||||
return CONTROLLER_NONE; //no idea what you're talking about.
|
||||
}
|
||||
|
||||
void INS_Rumble(int id, quint16_t amp_low, quint16_t amp_high, quint32_t duration)
|
||||
{
|
||||
//Con_DPrintf(CON_WARNING "Rumble is unavailable on this platform\n");
|
||||
|
|
|
@ -65,6 +65,16 @@ void INS_Commands (void); //final chance to call IN_MouseMove/IN_KeyEvent each f
|
|||
void INS_EnumerateDevices(void *ctx, void(*callback)(void *ctx, const char *type, const char *devicename, unsigned int *qdevid));
|
||||
void INS_SetupControllerAudioDevices(qboolean enabled); //creates audio devices for each controller (where controllers have their own audio devices)
|
||||
|
||||
enum controllertype_e
|
||||
{
|
||||
CONTROLLER_NONE,
|
||||
CONTROLLER_UNKNOWN, //its all just numbers. could be anything
|
||||
CONTROLLER_XBOX, //ABXY
|
||||
CONTROLLER_PLAYSTATION, //XOSqareTriangle
|
||||
CONTROLLER_NINTENDO, //BAYX...
|
||||
CONTROLLER_VIRTUAL //its fucked. no idea what mappings they're using at all. could be keyboard.
|
||||
};
|
||||
enum controllertype_e INS_GetControllerType(int id);
|
||||
void INS_Rumble(int joy, quint16_t amp_low, quint16_t amp_high, quint32_t duration);
|
||||
void INS_RumbleTriggers(int joy, quint16_t left, quint16_t right, quint32_t duration);
|
||||
void INS_SetLEDColor(int id, vec3_t color);
|
||||
|
|
|
@ -1274,4 +1274,232 @@ void QCBUILTIN PF_cl_SendPacket(pubprogfuncs_t *prinst, struct globalvars_s *pr_
|
|||
}
|
||||
|
||||
|
||||
void QCBUILTIN PF_cl_gp_querywithcb(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
|
||||
{ //for wrath compat, which isn't allowed to just return it for some reason.
|
||||
int device = G_FLOAT(OFS_PARM0);
|
||||
enum controllertype_e type = INS_GetControllerType(device);
|
||||
|
||||
func_t f = PR_FindFunction (prinst, "Controller_Type", PR_CURRENT);
|
||||
if (f)
|
||||
{
|
||||
G_FLOAT(OFS_PARM0) = device;
|
||||
G_FLOAT(OFS_PARM1) = type;
|
||||
PR_ExecuteProgram (prinst, f);
|
||||
}
|
||||
}
|
||||
void QCBUILTIN PF_cl_gp_getbuttontype(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
|
||||
{
|
||||
int device = G_FLOAT(OFS_PARM0);
|
||||
G_FLOAT(OFS_RETURN) = INS_GetControllerType(device);
|
||||
}
|
||||
void QCBUILTIN PF_cl_gp_rumble(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
|
||||
{
|
||||
int device = G_FLOAT(OFS_PARM0);
|
||||
quint16_t amp_low = G_FLOAT(OFS_PARM1);
|
||||
quint16_t amp_high = G_FLOAT(OFS_PARM2);
|
||||
quint32_t duration = G_FLOAT(OFS_PARM3);
|
||||
INS_Rumble(device, amp_low, amp_high, duration);
|
||||
}
|
||||
void QCBUILTIN PF_cl_gp_rumbletriggers(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
|
||||
{
|
||||
int device = G_FLOAT(OFS_PARM0);
|
||||
quint16_t left = G_FLOAT(OFS_PARM1);
|
||||
quint16_t right = G_FLOAT(OFS_PARM2);
|
||||
quint32_t duration = G_FLOAT(OFS_PARM3);
|
||||
INS_RumbleTriggers(device, left, right, duration);
|
||||
}
|
||||
void QCBUILTIN PF_cl_gp_setledcolor(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
|
||||
{
|
||||
int device = G_FLOAT(OFS_PARM0);
|
||||
INS_SetLEDColor(device, G_VECTOR(OFS_PARM1));
|
||||
}
|
||||
void QCBUILTIN PF_cl_gp_settriggerfx(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
|
||||
{
|
||||
int device = G_FLOAT(OFS_PARM0);
|
||||
int size = G_INT(OFS_PARM2);
|
||||
const void *fxptr = PR_GetReadQCPtr(prinst, G_INT(OFS_PARM1), size);
|
||||
|
||||
if (!fxptr)
|
||||
PR_BIError(prinst, "PF_cl_gp_settriggerfx: invalid pointer/size\n");
|
||||
else
|
||||
INS_SetTriggerFX(device, fxptr, size);
|
||||
}
|
||||
|
||||
const char *PF_cl_serverkey_internal(const char *keyname)
|
||||
{
|
||||
char *ret;
|
||||
static char adr[MAX_ADR_SIZE];
|
||||
|
||||
if (!strcmp(keyname, "ip"))
|
||||
{
|
||||
if (cls.demoplayback)
|
||||
ret = cls.lastdemoname;
|
||||
else
|
||||
ret = NET_AdrToString(adr, sizeof(adr), &cls.netchan.remote_address);
|
||||
}
|
||||
else if (!strcmp(keyname, "servername"))
|
||||
ret = cls.servername;
|
||||
else if (!strcmp(keyname, "constate"))
|
||||
{
|
||||
if (cls.state == ca_disconnected
|
||||
#ifndef CLIENTONLY
|
||||
&& !sv.state
|
||||
#endif
|
||||
)
|
||||
ret = "disconnected";
|
||||
else if (cls.state == ca_active)
|
||||
ret = "active";
|
||||
else
|
||||
ret = "connecting";
|
||||
}
|
||||
else if (!strcmp(keyname, "loadstate"))
|
||||
{
|
||||
extern int total_loading_size, current_loading_size, loading_stage;
|
||||
extern char levelshotname[MAX_QPATH];
|
||||
ret = va("%i %u %u \"%s\"", loading_stage, current_loading_size, total_loading_size, levelshotname);
|
||||
}
|
||||
else if (!strcmp(keyname, "transferring"))
|
||||
{
|
||||
ret = CL_TryingToConnect();
|
||||
if (!ret)
|
||||
ret = "";
|
||||
}
|
||||
else if (!strcmp(keyname, "maxplayers"))
|
||||
{
|
||||
ret = va("%i", cl.allocated_client_slots);
|
||||
}
|
||||
else if (!strcmp(keyname, "dlstate"))
|
||||
{
|
||||
if (!cl.downloadlist && !cls.download)
|
||||
ret = ""; //nothing being downloaded right now
|
||||
else
|
||||
{
|
||||
unsigned int fcount;
|
||||
qofs_t tsize;
|
||||
qboolean sizeextra;
|
||||
CL_GetDownloadSizes(&fcount, &tsize, &sizeextra);
|
||||
if (cls.download) //downloading something
|
||||
ret = va("%u %g %u \"%s\" \"%s\" %g %i %g %g", fcount, (float)tsize, sizeextra?1u:0u, cls.download->localname, cls.download->remotename, cls.download->percent, cls.download->rate, (float)cls.download->completedbytes, (float)cls.download->size);
|
||||
else //not downloading anything right now
|
||||
ret = va("%u %g %u", fcount, (float)tsize, sizeextra?1u:0u);
|
||||
}
|
||||
}
|
||||
else if (!strcmp(keyname, "pausestate"))
|
||||
ret = cl.paused?"1":"0";
|
||||
else if (!strcmp(keyname, "protocol"))
|
||||
{ //using this is pretty acedemic, really. Not particuarly portable.
|
||||
switch (cls.protocol)
|
||||
{ //a tokenizable string
|
||||
//first is the base game qw/nq
|
||||
//second is branch (custom engine name)
|
||||
//third is protocol version.
|
||||
default:
|
||||
case CP_UNKNOWN:
|
||||
ret = "Unknown";
|
||||
break;
|
||||
case CP_QUAKEWORLD:
|
||||
if (cls.fteprotocolextensions||cls.fteprotocolextensions2)
|
||||
ret = "QuakeWorld FTE";
|
||||
else if (cls.z_ext)
|
||||
ret = "QuakeWorld ZQuake";
|
||||
else
|
||||
ret = "QuakeWorld";
|
||||
break;
|
||||
case CP_NETQUAKE:
|
||||
switch (cls.protocol_nq)
|
||||
{
|
||||
default:
|
||||
ret = "NetQuake";
|
||||
break;
|
||||
case CPNQ_FITZ666:
|
||||
ret = "Fitz666";
|
||||
break;
|
||||
case CPNQ_DP5:
|
||||
ret = "NetQuake DarkPlaces 5";
|
||||
break;
|
||||
case CPNQ_DP6:
|
||||
ret = "NetQuake DarkPlaces 6";
|
||||
break;
|
||||
case CPNQ_DP7:
|
||||
ret = "NetQuake DarkPlaces 7";
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case CP_QUAKE2:
|
||||
ret = "Quake2";
|
||||
break;
|
||||
case CP_QUAKE3:
|
||||
ret = "Quake3";
|
||||
break;
|
||||
case CP_PLUGIN:
|
||||
ret = "External";
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (!strcmp(keyname, "challenge"))
|
||||
{
|
||||
ret = va("%u", cls.challenge);
|
||||
}
|
||||
else
|
||||
{
|
||||
#ifndef CLIENTONLY
|
||||
if (sv.state >= ss_loading)
|
||||
{
|
||||
ret = InfoBuf_ValueForKey(&svs.info, keyname);
|
||||
if (!*ret)
|
||||
ret = InfoBuf_ValueForKey(&svs.localinfo, keyname);
|
||||
}
|
||||
else
|
||||
#endif
|
||||
ret = InfoBuf_ValueForKey(&cl.serverinfo, keyname);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
//string(string keyname)
|
||||
void QCBUILTIN PF_cl_serverkey (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
|
||||
{
|
||||
const char *keyname = PF_VarString(prinst, 0, pr_globals);
|
||||
const char *ret = PF_cl_serverkey_internal(keyname);
|
||||
if (*ret)
|
||||
RETURN_TSTRING(ret);
|
||||
else
|
||||
G_INT(OFS_RETURN) = 0;
|
||||
}
|
||||
void QCBUILTIN PF_cl_serverkeyfloat (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
|
||||
{
|
||||
const char *keyname = PR_GetStringOfs(prinst, OFS_PARM0);
|
||||
const char *ret = PF_cl_serverkey_internal(keyname);
|
||||
if (*ret)
|
||||
G_FLOAT(OFS_RETURN) = strtod(ret, NULL);
|
||||
else
|
||||
G_FLOAT(OFS_RETURN) = (prinst->callargc >= 2)?G_FLOAT(OFS_PARM1):0;
|
||||
}
|
||||
void QCBUILTIN PF_cl_serverkeyblob (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
|
||||
{
|
||||
const char *keyname = PR_GetStringOfs(prinst, OFS_PARM0);
|
||||
int qcptr = G_INT(OFS_PARM1);
|
||||
int qcsize = G_INT(OFS_PARM2);
|
||||
void *ptr;
|
||||
size_t blobsize = 0;
|
||||
const char *blob;
|
||||
|
||||
if (qcptr < 0 || qcptr+qcsize >= prinst->stringtablesize)
|
||||
{
|
||||
PR_BIError(prinst, "PF_cs_serverkeyblob: invalid pointer\n");
|
||||
return;
|
||||
}
|
||||
ptr = (prinst->stringtable + qcptr);
|
||||
|
||||
blob = InfoBuf_BlobForKey(&cl.serverinfo, keyname, &blobsize, NULL);
|
||||
|
||||
if (qcptr)
|
||||
{
|
||||
blobsize = min(blobsize, qcsize);
|
||||
memcpy(ptr, blob, blobsize);
|
||||
G_INT(OFS_RETURN) = blobsize;
|
||||
}
|
||||
else
|
||||
G_INT(OFS_RETURN) = blobsize;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -4326,136 +4326,6 @@ static void CheckSendPings(void)
|
|||
}
|
||||
}
|
||||
|
||||
static const char *PF_cs_serverkey_internal(const char *keyname)
|
||||
{
|
||||
char *ret;
|
||||
static char adr[MAX_ADR_SIZE];
|
||||
|
||||
if (!strcmp(keyname, "ip"))
|
||||
{
|
||||
if (cls.demoplayback)
|
||||
ret = cls.lastdemoname;
|
||||
else
|
||||
ret = NET_AdrToString(adr, sizeof(adr), &cls.netchan.remote_address);
|
||||
}
|
||||
else if (!strcmp(keyname, "servername"))
|
||||
ret = cls.servername;
|
||||
else if (!strcmp(keyname, "constate"))
|
||||
{
|
||||
if (cls.state == ca_disconnected
|
||||
#ifndef CLIENTONLY
|
||||
&& !sv.state
|
||||
#endif
|
||||
)
|
||||
ret = "disconnected";
|
||||
else if (cls.state == ca_active)
|
||||
ret = "active";
|
||||
else
|
||||
ret = "connecting";
|
||||
}
|
||||
else if (!strcmp(keyname, "loadstate"))
|
||||
{
|
||||
extern int total_loading_size, current_loading_size, loading_stage;
|
||||
extern char levelshotname[MAX_QPATH];
|
||||
ret = va("%i %u %u \"%s\"", loading_stage, current_loading_size, total_loading_size, levelshotname);
|
||||
}
|
||||
else if (!strcmp(keyname, "transferring"))
|
||||
{
|
||||
ret = CL_TryingToConnect();
|
||||
if (!ret)
|
||||
ret = "";
|
||||
}
|
||||
else if (!strcmp(keyname, "maxplayers"))
|
||||
{
|
||||
ret = va("%i", cl.allocated_client_slots);
|
||||
}
|
||||
else if (!strcmp(keyname, "dlstate"))
|
||||
{
|
||||
if (!cl.downloadlist && !cls.download)
|
||||
ret = ""; //nothing being downloaded right now
|
||||
else
|
||||
{
|
||||
unsigned int fcount;
|
||||
qofs_t tsize;
|
||||
qboolean sizeextra;
|
||||
CL_GetDownloadSizes(&fcount, &tsize, &sizeextra);
|
||||
if (cls.download) //downloading something
|
||||
ret = va("%u %g %u \"%s\" \"%s\" %g %i %g %g", fcount, (float)tsize, sizeextra?1u:0u, cls.download->localname, cls.download->remotename, cls.download->percent, cls.download->rate, (float)cls.download->completedbytes, (float)cls.download->size);
|
||||
else //not downloading anything right now
|
||||
ret = va("%u %g %u", fcount, (float)tsize, sizeextra?1u:0u);
|
||||
}
|
||||
}
|
||||
else if (!strcmp(keyname, "pausestate"))
|
||||
ret = cl.paused?"1":"0";
|
||||
else if (!strcmp(keyname, "protocol"))
|
||||
{ //using this is pretty acedemic, really. Not particuarly portable.
|
||||
switch (cls.protocol)
|
||||
{ //a tokenizable string
|
||||
//first is the base game qw/nq
|
||||
//second is branch (custom engine name)
|
||||
//third is protocol version.
|
||||
default:
|
||||
case CP_UNKNOWN:
|
||||
ret = "Unknown";
|
||||
break;
|
||||
case CP_QUAKEWORLD:
|
||||
if (cls.fteprotocolextensions||cls.fteprotocolextensions2)
|
||||
ret = "QuakeWorld FTE";
|
||||
else if (cls.z_ext)
|
||||
ret = "QuakeWorld ZQuake";
|
||||
else
|
||||
ret = "QuakeWorld";
|
||||
break;
|
||||
case CP_NETQUAKE:
|
||||
switch (cls.protocol_nq)
|
||||
{
|
||||
default:
|
||||
ret = "NetQuake";
|
||||
break;
|
||||
case CPNQ_FITZ666:
|
||||
ret = "Fitz666";
|
||||
break;
|
||||
case CPNQ_DP5:
|
||||
ret = "NetQuake DarkPlaces 5";
|
||||
break;
|
||||
case CPNQ_DP6:
|
||||
ret = "NetQuake DarkPlaces 6";
|
||||
break;
|
||||
case CPNQ_DP7:
|
||||
ret = "NetQuake DarkPlaces 7";
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case CP_QUAKE2:
|
||||
ret = "Quake2";
|
||||
break;
|
||||
case CP_QUAKE3:
|
||||
ret = "Quake3";
|
||||
break;
|
||||
case CP_PLUGIN:
|
||||
ret = "External";
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (!strcmp(keyname, "challenge"))
|
||||
{
|
||||
ret = va("%u", cls.challenge);
|
||||
}
|
||||
else
|
||||
{
|
||||
#ifndef CLIENTONLY
|
||||
if (sv.state >= ss_loading)
|
||||
{
|
||||
ret = InfoBuf_ValueForKey(&svs.info, keyname);
|
||||
if (!*ret)
|
||||
ret = InfoBuf_ValueForKey(&svs.localinfo, keyname);
|
||||
}
|
||||
else
|
||||
#endif
|
||||
ret = InfoBuf_ValueForKey(&cl.serverinfo, keyname);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
static const char *PF_cs_getplayerkey_internal (unsigned int pnum, const char *keyname)
|
||||
{
|
||||
static char buffer[64];
|
||||
|
@ -4567,53 +4437,6 @@ static const char *PF_cs_getplayerkey_internal (unsigned int pnum, const char *k
|
|||
return ret;
|
||||
}
|
||||
|
||||
//string(string keyname)
|
||||
static void QCBUILTIN PF_cs_serverkey (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
|
||||
{
|
||||
const char *keyname = PF_VarString(prinst, 0, pr_globals);
|
||||
const char *ret = PF_cs_serverkey_internal(keyname);
|
||||
if (*ret)
|
||||
RETURN_TSTRING(ret);
|
||||
else
|
||||
G_INT(OFS_RETURN) = 0;
|
||||
}
|
||||
static void QCBUILTIN PF_cs_serverkeyfloat (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
|
||||
{
|
||||
const char *keyname = PR_GetStringOfs(prinst, OFS_PARM0);
|
||||
const char *ret = PF_cs_serverkey_internal(keyname);
|
||||
if (*ret)
|
||||
G_FLOAT(OFS_RETURN) = strtod(ret, NULL);
|
||||
else
|
||||
G_FLOAT(OFS_RETURN) = (prinst->callargc >= 2)?G_FLOAT(OFS_PARM1):0;
|
||||
}
|
||||
static void QCBUILTIN PF_cs_serverkeyblob (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
|
||||
{
|
||||
const char *keyname = PR_GetStringOfs(prinst, OFS_PARM0);
|
||||
int qcptr = G_INT(OFS_PARM1);
|
||||
int qcsize = G_INT(OFS_PARM2);
|
||||
void *ptr;
|
||||
size_t blobsize = 0;
|
||||
const char *blob;
|
||||
|
||||
if (qcptr < 0 || qcptr+qcsize >= prinst->stringtablesize)
|
||||
{
|
||||
PR_BIError(prinst, "PF_cs_serverkeyblob: invalid pointer\n");
|
||||
return;
|
||||
}
|
||||
ptr = (prinst->stringtable + qcptr);
|
||||
|
||||
blob = InfoBuf_BlobForKey(&cl.serverinfo, keyname, &blobsize, NULL);
|
||||
|
||||
if (qcptr)
|
||||
{
|
||||
blobsize = min(blobsize, qcsize);
|
||||
memcpy(ptr, blob, blobsize);
|
||||
G_INT(OFS_RETURN) = blobsize;
|
||||
}
|
||||
else
|
||||
G_INT(OFS_RETURN) = blobsize;
|
||||
}
|
||||
|
||||
//string(float pnum, string keyname)
|
||||
static void QCBUILTIN PF_cs_getplayerkeystring (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
|
||||
{
|
||||
|
@ -4714,7 +4537,7 @@ static void QCBUILTIN PF_cs_infokey (pubprogfuncs_t *prinst, struct globalvars_s
|
|||
const char *keyname = PR_GetStringOfs(prinst, OFS_PARM1);
|
||||
const char *ret;
|
||||
if (!ent->entnum)
|
||||
ret = PF_cs_serverkey_internal(keyname);
|
||||
ret = PF_cl_serverkey_internal(keyname);
|
||||
else
|
||||
{
|
||||
int pnum = ent->xv->entnum-1; //figure out which ssqc entity this is meant to be
|
||||
|
@ -6779,42 +6602,6 @@ static void QCBUILTIN PF_resourcestatus(pubprogfuncs_t *prinst, struct globalvar
|
|||
}
|
||||
}
|
||||
|
||||
static void QCBUILTIN PF_cl_gp_rumble(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
|
||||
{
|
||||
int device = G_FLOAT(OFS_PARM0);
|
||||
quint16_t amp_low = G_FLOAT(OFS_PARM1);
|
||||
quint16_t amp_high = G_FLOAT(OFS_PARM2);
|
||||
quint32_t duration = G_FLOAT(OFS_PARM3);
|
||||
INS_Rumble(device, amp_low, amp_high, duration);
|
||||
}
|
||||
|
||||
static void QCBUILTIN PF_cl_gp_rumbletriggers(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
|
||||
{
|
||||
int device = G_FLOAT(OFS_PARM0);
|
||||
quint16_t left = G_FLOAT(OFS_PARM1);
|
||||
quint16_t right = G_FLOAT(OFS_PARM2);
|
||||
quint32_t duration = G_FLOAT(OFS_PARM3);
|
||||
INS_RumbleTriggers(device, left, right, duration);
|
||||
}
|
||||
|
||||
static void QCBUILTIN PF_cl_gp_setledcolor(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
|
||||
{
|
||||
int device = G_FLOAT(OFS_PARM0);
|
||||
INS_SetLEDColor(device, G_VECTOR(OFS_PARM1));
|
||||
}
|
||||
|
||||
static void QCBUILTIN PF_cl_gp_settriggerfx(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
|
||||
{
|
||||
int device = G_FLOAT(OFS_PARM0);
|
||||
int size = G_INT(OFS_PARM2);
|
||||
const void *fxptr = PR_GetReadQCPtr(prinst, G_INT(OFS_PARM1), size);
|
||||
|
||||
if (!fxptr)
|
||||
PR_BIError(prinst, "PF_cl_gp_settriggerfx: invalid pointer/size\n");
|
||||
else
|
||||
INS_SetTriggerFX(device, fxptr, size);
|
||||
}
|
||||
|
||||
/*static void PF_cs_clipboard_got(void *ctx, const char *utf8)
|
||||
{
|
||||
void *pr_globals;
|
||||
|
@ -7283,9 +7070,9 @@ static struct {
|
|||
{"registercommand", PF_cs_registercommand, 352}, // #352 void(string cmdname) registercommand (EXT_CSQC)
|
||||
{"wasfreed", PF_WasFreed, 353}, // #353 float(entity ent) wasfreed (EXT_CSQC) (should be availabe on server too)
|
||||
|
||||
{"serverkey", PF_cs_serverkey, 354}, // #354 string(string key) serverkey;
|
||||
{"serverkeyfloat", PF_cs_serverkeyfloat, 0}, // #0 float(string key) serverkeyfloat;
|
||||
{"serverkeyblob", PF_cs_serverkeyblob, 0},
|
||||
{"serverkey", PF_cl_serverkey, 354}, // #354 string(string key) serverkey;
|
||||
{"serverkeyfloat", PF_cl_serverkeyfloat, 0}, // #0 float(string key) serverkeyfloat;
|
||||
{"serverkeyblob", PF_cl_serverkeyblob, 0},
|
||||
{"getentitytoken", PF_cs_getentitytoken, 355}, // #355 string() getentitytoken;
|
||||
{"findfont", PF_CL_findfont, 356},
|
||||
{"loadfont", PF_CL_loadfont, 357},
|
||||
|
@ -7630,10 +7417,23 @@ static struct {
|
|||
{"fremove", PF_fremove, 652},
|
||||
{"fexists", PF_fexists, 653},
|
||||
{"rmtree", PF_rmtree, 654},
|
||||
{"gp_rumble", PF_cl_gp_rumble, 0}, // #0 void(float devid, float amp_low, float amp_high, float duration) gp_rumble
|
||||
{"gp_rumbletriggers", PF_cl_gp_rumbletriggers, 0}, // #0 void(float devid, float left, float right, float duration) gp_rumbletriggers
|
||||
{"gp_setledcolor", PF_cl_gp_setledcolor, 0}, // #0 void(float devid, float red, float green, float blue) gp_setledcolor
|
||||
{"gp_settriggerfx", PF_cl_gp_settriggerfx, 0}, // #0 void(float devid, const void *data, int size) gp_settriggerfx
|
||||
|
||||
{"stachievement_unlock", PF_Fixme, 730}, // EXT_STEAM_REKI
|
||||
{"stachievement_query", PF_Fixme, 731}, // EXT_STEAM_REKI
|
||||
{"ststat_setvalue", PF_Fixme, 732}, // EXT_STEAM_REKI
|
||||
{"ststat_increment", PF_Fixme, 733}, // EXT_STEAM_REKI
|
||||
{"ststat_query", PF_Fixme, 734}, // EXT_STEAM_REKI
|
||||
{"stachievement_register", PF_Fixme, 735}, // EXT_STEAM_REKI
|
||||
{"ststat_register", PF_Fixme, 736}, // EXT_STEAM_REKI
|
||||
{"controller_query", PF_cl_gp_querywithcb, 740}, // EXT_CONTROLLER_REKI
|
||||
{"controller_rumble", PF_cl_gp_rumble, 741}, // EXT_CONTROLLER_REKI
|
||||
{"controller_rumbletriggers",PF_cl_gp_rumbletriggers, 742}, // EXT_CONTROLLER_REKI
|
||||
|
||||
{"gp_getlayout", PF_cl_gp_getbuttontype, 0}, // #0 float(float devid) gp_getbuttontype
|
||||
{"gp_rumble", PF_cl_gp_rumble, 0}, // #0 void(float devid, float amp_low, float amp_high, float duration) gp_rumble
|
||||
{"gp_rumbletriggers", PF_cl_gp_rumbletriggers, 0}, // #0 void(float devid, float left, float right, float duration) gp_rumbletriggers
|
||||
{"gp_setledcolor", PF_cl_gp_setledcolor, 0}, // #0 void(float devid, float red, float green, float blue) gp_setledcolor
|
||||
{"gp_settriggerfx", PF_cl_gp_settriggerfx, 0}, // #0 void(float devid, const void *data, int size) gp_settriggerfx
|
||||
|
||||
{NULL}
|
||||
};
|
||||
|
|
|
@ -2631,12 +2631,18 @@ static struct {
|
|||
// {NULL, PF_Fixme, 346},
|
||||
// {NULL, PF_Fixme, 347},
|
||||
// {NULL, PF_Fixme, 348},
|
||||
{"setlocaluserinfo", PF_cl_setlocaluserinfo, 0},
|
||||
{"getlocaluserinfo", PF_cl_getlocaluserinfostring, 0},
|
||||
{"setlocaluserinfoblob", PF_cl_setlocaluserinfo, 0},
|
||||
{"getlocaluserinfoblob", PF_cl_getlocaluserinfoblob, 0},
|
||||
{"isdemo", PF_isdemo, 349},
|
||||
// {NULL, PF_Fixme, 350},
|
||||
// {NULL, PF_Fixme, 351},
|
||||
{"registercommand", PF_menu_registercommand, 352},
|
||||
{"wasfreed", PF_WasFreed, 353},
|
||||
// {NULL, PF_Fixme, 354},
|
||||
{"serverkey", PF_cl_serverkey, 354}, // #354 string(string key) serverkey;
|
||||
{"serverkeyfloat", PF_cl_serverkeyfloat, 0}, // #0 float(string key) serverkeyfloat;
|
||||
{"serverkeyblob", PF_cl_serverkeyblob, 0},
|
||||
#ifdef HAVE_MEDIA_DECODER
|
||||
{"videoplaying", PF_cs_media_getstate, 355},
|
||||
#endif
|
||||
|
@ -2839,11 +2845,22 @@ static struct {
|
|||
{"fexists", PF_fexists, 653},
|
||||
{"rmtree", PF_rmtree, 654},
|
||||
|
||||
{"stachievement_unlock", PF_Fixme, 730}, // EXT_STEAM_REKI
|
||||
{"stachievement_query", PF_Fixme, 731}, // EXT_STEAM_REKI
|
||||
{"ststat_setvalue", PF_Fixme, 732}, // EXT_STEAM_REKI
|
||||
{"ststat_increment", PF_Fixme, 733}, // EXT_STEAM_REKI
|
||||
{"ststat_query", PF_Fixme, 734}, // EXT_STEAM_REKI
|
||||
{"stachievement_register", PF_Fixme, 735}, // EXT_STEAM_REKI
|
||||
{"ststat_register", PF_Fixme, 736}, // EXT_STEAM_REKI
|
||||
{"controller_query", PF_cl_gp_querywithcb, 740}, // EXT_CONTROLLER_REKI
|
||||
{"controller_rumble", PF_cl_gp_rumble, 741}, // EXT_CONTROLLER_REKI
|
||||
{"controller_rumbletriggers",PF_cl_gp_rumbletriggers, 742}, // EXT_CONTROLLER_REKI
|
||||
|
||||
{"setlocaluserinfo", PF_cl_setlocaluserinfo, 0},
|
||||
{"getlocaluserinfo", PF_cl_getlocaluserinfostring, 0},
|
||||
{"setlocaluserinfoblob", PF_cl_setlocaluserinfo, 0},
|
||||
{"getlocaluserinfoblob", PF_cl_getlocaluserinfoblob, 0},
|
||||
{"gp_getlayout", PF_cl_gp_getbuttontype, 0}, // #0 float(float devid) gp_getbuttontype
|
||||
{"gp_rumble", PF_cl_gp_rumble, 0}, // #0 void(float devid, float amp_low, float amp_high, float duration) gp_rumble
|
||||
{"gp_rumbletriggers", PF_cl_gp_rumbletriggers, 0}, // #0 void(float devid, float left, float right, float duration) gp_rumbletriggers
|
||||
{"gp_setledcolor", PF_cl_gp_setledcolor, 0}, // #0 void(float devid, float red, float green, float blue) gp_setledcolor
|
||||
{"gp_settriggerfx", PF_cl_gp_settriggerfx, 0}, // #0 void(float devid, const void *data, int size) gp_settriggerfx
|
||||
|
||||
{NULL}
|
||||
};
|
||||
|
|
|
@ -86,6 +86,10 @@ void INS_ReInit(void)
|
|||
void INS_Shutdown(void)
|
||||
{
|
||||
}
|
||||
enum controllertype_e INS_GetControllerType(int id)
|
||||
{
|
||||
return CONTROLLER_NONE;
|
||||
}
|
||||
void INS_Rumble(int joy, uint16_t amp_low, uint16_t amp_high, uint32_t duration)
|
||||
{
|
||||
}
|
||||
|
|
|
@ -538,6 +538,17 @@ void QCBUILTIN PF_cl_SendPacket(pubprogfuncs_t *prinst, struct globalvars_s *pr_
|
|||
void QCBUILTIN PF_cl_getlocaluserinfoblob (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals);
|
||||
void QCBUILTIN PF_cl_getlocaluserinfostring (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals);
|
||||
void QCBUILTIN PF_cl_setlocaluserinfo (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals);
|
||||
const char *PF_cl_serverkey_internal(const char *keyname);
|
||||
void QCBUILTIN PF_cl_serverkey (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals);
|
||||
void QCBUILTIN PF_cl_serverkeyfloat (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals);
|
||||
void QCBUILTIN PF_cl_serverkeyblob (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals);
|
||||
|
||||
void QCBUILTIN PF_cl_gp_querywithcb(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals);
|
||||
void QCBUILTIN PF_cl_gp_getbuttontype(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals);
|
||||
void QCBUILTIN PF_cl_gp_rumble(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals);
|
||||
void QCBUILTIN PF_cl_gp_rumbletriggers(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals);
|
||||
void QCBUILTIN PF_cl_gp_setledcolor(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals);
|
||||
void QCBUILTIN PF_cl_gp_settriggerfx(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals);
|
||||
|
||||
void search_close_progs(pubprogfuncs_t *prinst, qboolean complain);
|
||||
|
||||
|
|
|
@ -5083,6 +5083,10 @@ void INS_EnumerateDevices(void *ctx, void(*callback)(void *ctx, const char *type
|
|||
#endif
|
||||
}
|
||||
|
||||
enum controllertype_e INS_GetControllerType(int id)
|
||||
{
|
||||
return CONTROLLER_NONE;
|
||||
}
|
||||
/* doubt this will ever happen to begin with */
|
||||
void INS_Rumble(int id, quint16_t amp_low, quint16_t amp_high, quint32_t duration)
|
||||
{
|
||||
|
|
|
@ -12019,8 +12019,10 @@ static BuiltinList_t BuiltinList[] = { //nq qw h2 ebfs
|
|||
{"applycustomskin", PF_Fixme, 0, 0, 0, 378, D("void(entity e, float skinobj)", "Updates the entity's custom skin (refcounted).")},
|
||||
{"releasecustomskin",PF_Fixme, 0, 0, 0, 379, D("void(float skinobj)", "Lets the engine know that the skin will no longer be needed. Thanks to refcounting any ents with the skin already applied will retain their skin until later changed. It is valid to destroy a skin just after applying it to an ent in the same function that it was created in, as the skin will only be destroyed once its refcount rops to 0.")},
|
||||
|
||||
{"gp_rumble", PF_Fixme, 0, 0, 0, 0, D("void(float devid, float amp_low, float amp_high, float duration)", "Sends a single rumble event to the game-pad specified in devid. Every time you call this, the previous effect is cancelled out.")},
|
||||
{"gp_rumbletriggers", PF_Fixme, 0, 0, 0, 0, D("void(float devid, float left, float right, float duration)", "Makes the analog triggers rumble of the specified game-pad, like gp_rumble() one call cancels out the previous one on the device.")},
|
||||
{"gp_getbuttontype",PF_Fixme, 0, 0, 0, 0, D("enum controllertype : float\n{\n\tCONTROLLER_NONE,\n\tCONTROLLER_UNKNOWN,\n\tCONTROLLER_XBOX,\n\tCONTROLLER_PLAYSTATION,\n\tCONTROLLER_NINTENDO,\n\tCONTROLLER_VIRTUAL};\n"
|
||||
"json_type_e(float devid)", "Sends a single rumble event to the game-pad specified in devid. Every time you call this, the previous effect is cancelled out.")},
|
||||
{"gp_rumble", PF_Fixme, 0, 0, 0, 0, D("void(float devid, float amp_low, float amp_high, float duration)", "Sends a single rumble event to the game-pad specified in devid. Every time you call this, the previous effect is cancelled out.")},
|
||||
{"gp_rumbletriggers",PF_Fixme, 0, 0, 0, 0, D("void(float devid, float left, float right, float duration)", "Makes the analog triggers rumble of the specified game-pad, like gp_rumble() one call cancels out the previous one on the device.")},
|
||||
{"gp_setledcolor", PF_Fixme, 0, 0, 0, 0, D("void(float devid, vector color)", "Updates the game-pad LED color.")},
|
||||
{"gp_settriggerfx", PF_Fixme, 0, 0, 0, 0, D("void(float devid, /*const*/ void *data, int size)", "Sends a specific effect packet to the controller. On the PlayStation 5's DualSense that can adjust the tension on the analog triggers.")},
|
||||
//END EXT_CSQC
|
||||
|
@ -12351,6 +12353,43 @@ static BuiltinList_t BuiltinList[] = { //nq qw h2 ebfs
|
|||
{"fexists", PF_fexists, 0, 0, 0, 653, D("float(string fname)", "Returns true if it exists inside the default writable path. Use whichpack for greater portability.")},
|
||||
{"rmtree", PF_rmtree, 0, 0, 0, 654, D("float(string path)", "Dangerous, but sandboxed to data/")},
|
||||
{"walkmovedist", PF_walkmovedist, 0, 0, 0, 655, D("DEP float(float yaw, float dist, optional float settraceglobals)", "Attempt to walk the entity at a given angle for a given distance.\nif settraceglobals is set, the trace_* globals will be set, showing the results of the movement.\nThis function will trigger touch events."), true},
|
||||
{"timescale", PF_Fixme, 0, 0, 0, 656, "DEP void(float f)" STUB},
|
||||
{"nodegraph_graphset_clear", PF_Fixme,0,0,0,700, "DEP float()" STUB}, //Wrath's EXT_NODEGRAPH
|
||||
{"nodegraph_graphset_load", PF_Fixme,0,0,0,701, "DEP float()" STUB},
|
||||
{"nodegraph_graphset_save", PF_Fixme,0,0,0,702, "DEP float()" STUB},
|
||||
{"nodegraph_graph_clear", PF_Fixme,0,0,0,703, "DEP float(float graphid)" STUB},
|
||||
{"nodegraph_graph_nodes_count", PF_Fixme,0,0,0,704, "DEP float(float graphid)" STUB},
|
||||
{"nodegraph_graph_add_node", PF_Fixme,0,0,0,705, "DEP float(float graphid, vector node)" STUB},
|
||||
{"nodegraph_graph_remove_node", PF_Fixme,0,0,0,706, "DEP float(float graphid, float nodeid)" STUB},
|
||||
{"nodegraph_graph_is_node_valid", PF_Fixme,0,0,0,707, "DEP float(float graphid, float nodeid)" STUB},
|
||||
{"nodegraph_graph_get_node", PF_Fixme,0,0,0,708, "DEP vector(float graphid, float nodeid)" STUB},
|
||||
{"nodegraph_graph_add_link", PF_Fixme,0,0,0,709, "DEP float(float graphid, float nodeidfrom, float nodeidto)" STUB},
|
||||
{"nodegraph_graph_remove_link", PF_Fixme,0,0,0,710, "DEP float(float graphid, float nodeidfrom, float nodeidto)" STUB},
|
||||
{"nodegraph_graph_does_link_exist", PF_Fixme,0,0,0,711, "DEP float(float graphid, float nodeidfrom, float nodeidto)" STUB},
|
||||
{"nodegraph_graph_find_nearest_nodeid", PF_Fixme,0,0,0,712, "DEP float(float graphid, vector position)" STUB},
|
||||
{"nodegraph_graph_query_path", PF_Fixme,0,0,0,713, "DEP float(float graphid, float nodeidfrom, float nodeidto)" STUB},
|
||||
{"nodegraph_graph_query_nodes_linked", PF_Fixme,0,0,0,714, "DEP float(float graphid, float nodeid)" STUB},
|
||||
{"nodegraph_graph_query_nodes_in_radius", PF_Fixme,0,0,0,715, "DEP float(float graphid, vector position, float radius)" STUB},
|
||||
{"nodegraph_query_release", PF_Fixme,0,0,0,716, "DEP float(float graphid)" STUB},
|
||||
{"nodegraph_query_entries_count", PF_Fixme,0,0,0,717, "DEP float(float graphid)" STUB},
|
||||
{"nodegraph_query_is_valid", PF_Fixme,0,0,0,718, "DEP float(float graphid)" STUB},
|
||||
{"nodegraph_query_get_graphid", PF_Fixme,0,0,0,719, "DEP float(float graphid)" STUB},
|
||||
{"nodegraph_query_get_nodeid", PF_Fixme,0,0,0,720, "DEP float(float graphid, float entryid)" STUB},
|
||||
{"nodegraph_moveprobe_fly", PF_Fixme,0,0,0,721, "DEP float(vector nodefrom, vector nodeto, vector mins, vector maxs, float type)" STUB},
|
||||
{"nodegraph_moveprobe_walk", PF_Fixme,0,0,0,722, "DEP float(vector nodefrom, vector nodeto, vector mins, vector maxs, float stepheight, float dropheight)" STUB},
|
||||
{"nodegraph_graph_query_nodes_in_radius_fly_reachable", PF_Fixme,0,0,0,723, "DEP float(float graphid, vector position, float radius, vector mins, vector maxs, float type)" STUB},
|
||||
{"nodegraph_graph_query_nodes_in_radius_walk_reachable",PF_Fixme,0,0,0,724, "DEP float(float graphid, vector position, float radius, vector mins, vector maxs, float stepheight, float dropheight)" STUB},
|
||||
{"nodegraph_graphset_remove", PF_Fixme,0,0,0,725, "DEP float()" STUB},
|
||||
{"stachievement_unlock", PF_Fixme,0,0,0,730, "DEP void(string achievement_id)" STUB}, //(csqc) EXT_STEAM_REKI
|
||||
{"stachievement_query", PF_Fixme,0,0,0,731, "DEP void(string achievement_id)" STUB}, //(csqc) EXT_STEAM_REKI
|
||||
{"ststat_setvalue", PF_Fixme,0,0,0,732, "DEP void(string stat_id, float value)" STUB}, //(csqc) EXT_STEAM_REKI
|
||||
{"ststat_increment", PF_Fixme,0,0,0,733, "DEP void(string stat_id, float value)" STUB}, //(csqc) EXT_STEAM_REKI
|
||||
{"ststat_query", PF_Fixme,0,0,0,734, "DEP void(string stat_id)" STUB}, //(csqc) EXT_STEAM_REKI
|
||||
{"stachievement_register", PF_Fixme,0,0,0,735, "DEP void(string achievement_id)" STUB}, //(csqc) EXT_STEAM_REKI
|
||||
{"ststat_register", PF_Fixme,0,0,0,736, "DEP void(string stat_id)" STUB}, //(csqc) EXT_STEAM_REKI
|
||||
{"controller_query", PF_Fixme,0,0,0,740, "DEP void(float index)" STUB}, //(csqc) EXT_CONTROLLER_REKI
|
||||
{"controller_rumble", PF_Fixme,0,0,0,741, "DEP void(float index, float lowmult, float highmult, float )" STUB}, //(csqc) EXT_CONTROLLER_REKI
|
||||
{"controller_rumbletriggers", PF_Fixme,0,0,0,742, "DEP void(float index, float leftmult, float rightmult, float msec)" STUB}, //(csqc) EXT_CONTROLLER_REKI
|
||||
//end wrath extras
|
||||
|
||||
{"getrmqeffectsversion",PF_Ignore, 0, 0, 0, 666, "DEP float()" STUB},
|
||||
|
|
|
@ -649,6 +649,16 @@ void INS_EnumerateDevices(void *ctx, void(*callback)(void *ctx, const char *type
|
|||
}
|
||||
}
|
||||
|
||||
enum controllertype_e INS_GetControllerType(int id)
|
||||
{
|
||||
size_t i;
|
||||
for (i = 0; i < countof(gamepaddeviceids); i++)
|
||||
{
|
||||
if (id == gamepaddeviceids[i])
|
||||
return CONTROLLER_UNKNOWN; //browsers don't really like providing more info, to thwart fingerprinting. shame. you should just use generic glyphs.
|
||||
}
|
||||
return CONTROLLER_NONE; //nuffin here. yay fingerprinting?
|
||||
}
|
||||
void INS_Rumble(int joy, quint16_t amp_low, quint16_t amp_high, quint32_t duration)
|
||||
{
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue