fteqw/quakec/menusys/menu/newgame.qc
2023-01-09 05:15:06 +00:00

474 lines
16 KiB
C++

//maxclients is a QW thing. NQ engines use maxplayers (which requires a disconnect to apply)
#include "../menusys/mitem_grid.qc"
static string newgameinfo;
nonstatic void(mitem_desktop desktop) M_Dir =
{ //implementing this primarily so its available in QSS. :P
string path = argv(1);
string pack = argv(2);
searchhandle h;
if (path == "")
{
print("m_dir <FILEPATH> [PACKAGE]\n");
return;
}
if (pack)
h = search_begin(path, SB_FULLPACKAGEPATH|SB_FORCESEARCH|16, 0, argv(2));
else
h = search_begin(path, SB_FULLPACKAGEPATH|16|32, 0);
print(sprintf("Directory listing of %S (%g files)\n", path, search_getsize(h)));
for (float i = 0; i < search_getsize(h); i++)
{
float sz = search_getfilesize(h,i);
string szfmt = "b";
if (sz > 512*1024*1024)
sz /= 1024*1024*1024, szfmt = "gb";
else if (sz > 512*1024)
sz /= 1024*1024, szfmt = "mb";
else if (sz > 512)
sz /= 1024, szfmt = "kb";
print(sprintf(" %S (%.4g %s, %s, %S)\n",
search_getfilename(h,i),
sz,szfmt,
search_getfilemtime(h,i),
search_getpackagename(h,i)
));
#if 1
float f = search_fopen(h,i);
if (f>=0)
{
print(sprintf("^`u8:" "\t%S\n", substring(fgets(f), 0, 64)));
fclose(f);
}
else
print("\tsearch_fopen failure\n");
#endif
}
search_end(h);
};
nonstatic void(mitem_desktop desktop) M_FOpen =
{
float f = fopen(argv(1), FILE_READ);
string s;
if (f>=0)
{
while ((s=fgets(f)))
print(sprintf("^`u8:" "%s\n", s));
fclose(f);
print("<EOF>\n");
}
else
print("fopen failure\n");
};
#define FOURCC(a,b,c,d) ((int)(a)<<0i)|((int)(b)<<8i)|((int)(c)<<16i)|((int)(d)<<24i)
static string(string name) getmapdesc =
{
if (checkbuiltin(fread) && checkbuiltin(fseek) && checkbuiltin(memalloc) && checkbuiltin(memfree) && checkbuiltin(memgetval) && checkbuiltin(itof) && checkbuiltin(ftoi))
{ //we can do it, so do it.
filestream f = fopen(name, FILE_READ);
if (f < 0)
print(sprintf("Unable to read %s\n", name));
name = substring(name, 5, -5);
if (f < 0)
return name;
int *header = memalloc(sizeof(int)*4);
int bspver = 0, entofs, entlen;
if (fread(f, (void*)header, sizeof(int)*4) == sizeof(int)*4 && ((bspver=header[0]),(
bspver == FOURCC('I','B','S','P') || //IBSP (q2/q3)
bspver == FOURCC('R','B','S','P') || //RBSP (jk2o etc)
bspver == FOURCC('F','B','S','P') || //IBSP (qfusion/warsow)
bspver == 29i || //q1
bspver == 30i || //hl
bspver == FOURCC('B','S','P','2')))) //bsp2
{
if (bspver == FOURCC('I','B','S','P'))
{ //has an actual version number! ooo...
entofs = header[2];
entlen = header[3];
}
else
{
entofs = header[1];
entlen = header[2];
}
fseek(f, entofs);
string s = (string)memalloc(entlen+1);
fread(f, (void*)s, entlen);
float argc = tokenize(s);
if (argv(0) == "{")
{
for (float p = 1; p < argc; p+=2)
{
string t = argv(p);
if (t == "message")
{ //finally found the human-readable name of the map. woo.
name = argv(p+1);
break;
}
if (t == "}") //don't read the message from some kind of trigger
break;
if (t == "{") //some sort of corruption
break;
}
}
else
name = "ERROR";
memfree((void*)s);
}
else
name = sprintf("UNSUPPORTED %i", bspver);
memfree(header);
fclose(f);
return name;
}
return ""; //we can't do that in this engine... don't show anything, because its pointless showing the same thing twice.
};
static string(string name) packagetogamedir =
{
float so = strstrofs(name, "/");
if (so>=0)
name = substring(name, 0, so);
//don't hide id1/dm4 etc just because the user previously auto-downloaded eg qw/aerowalk
name = strtolower(name);
if (name == "qw" || name == "fte")
name = "id1";
return name;
};
class mitem_maplist : mitem_grid
{
strbuf names;
strbuf descs;
virtual void() item_remove =
{
buf_del(names);
buf_del(descs);
super::item_remove();
};
void() mitem_maplist =
{
searchhandle searchtable;
searchtable = search_begin("maps/*.bsp", SB_FULLPACKAGEPATH, 0);
float files = search_getsize(searchtable);
if (files && checkbuiltin(search_getpackagename))
{ //find which gamedir the first entry is in (highest-priority is first)
string gd = packagetogamedir(search_getpackagename(searchtable,0));
for (float count = 1; count < files; count++)
{
string mgd = packagetogamedir(search_getpackagename(searchtable,count));
if (mgd != gd)
{ //the gamedir changed... truncate the list here.
files = count;
break;
}
}
}
grid_numchildren = 0;
names = buf_create();
for (float count = 0; count < files; count++)
{
string n = search_getfilename(searchtable, count);
string shortname = substring(n, 5, -5);
if (!strncmp(shortname, "_", 1) || !strncmp(shortname, "b_", 2))
continue; //don't add b_* names
bufstr_set(names, grid_numchildren, n);
grid_numchildren++;
}
search_end(searchtable);
buf_sort(names, 0, FALSE); //make sure they're sorted now.
descs = buf_create();
item_resized(); //FIXME: is this needed?
grid_kactive = grid_numchildren?0:-1;
grid_selectionchanged(-1,grid_kactive);
};
virtual void(vector pos, float idx) grid_draw =
{
string map = bufstr_get(names, idx);
string desc = bufstr_get(descs, idx);
if not(desc)
{
desc = map;
static float lasttime;
if (time != lasttime)
{ //fix up the name and cache it.
lasttime = time;
desc = getmapdesc(map);
bufstr_set(descs, idx, desc);
}
}
map = substring(map, 5, -5);
vector col = (map == get("map"))?[1.16, 0.54, 0.41]:'1 1 1';
if (item_flags & IF_MFOCUSED && idx == grid_mactive)
col_z = 0;
if (item_flags & IF_KFOCUSED && idx == grid_kactive)
col_x = 0;
vector valuepos = [pos_x+item_size_x/2, pos_y];
pos_x = valuepos_x - stringwidth(map, TRUE, '1 1 0'*this.item_scale) - 8;
valuepos_x += 1;
ui.drawstring(pos, map, '1 1 0' * item_scale, col, item_alpha, 0);
ui.drawstring(valuepos, desc, '1 1 0' * item_scale, col, item_alpha, 0);
};
virtual float(vector pos, float scan, float chr, float down, float idx) grid_keypress =
{
if ((scan == K_ENTER || scan == K_MOUSE1) && down)
{
string map = bufstr_get(names, idx);
map = substring(map, 5, -5);
set("map", map);
return TRUE;
}
return FALSE;
};
// virtual void(float olditem, float newitem) grid_selectionchanged;
};
class mitem_newgame : mitem_exmenu
{
virtual float(string key) isvalid =
{
return TRUE;
};
virtual string(string key) get =
{
return infoget(newgameinfo, key);
};
virtual void(string key, string newval) set =
{
string old = newgameinfo;
newgameinfo = strzone(infoadd(newgameinfo, key, newval));
if (old)
strunzone(old);
};
};
nonstatic void(mitem_desktop desktop) M_NewGame =
{
mitem_pic banner;
string gametype = argv(1);
local float pos;
mitem_exmenu m;
if (gametype == "sp")
{
//single player has no options. the start map itself gives skill+episode options.
//use 0 for maxplayers. this allows splitscreen to allocate more.
localcmd("\ndisconnect; deathmatch 0; coop 0; maxplayers 0; timelimit 0; fraglimit 0; teamplay 0; samelevel 0; startmap_sp\n");
return;
}
if (gametype == "begin")
{
cvar_set("hostname", infoget(newgameinfo, "hostname"));
cvar_set("deathmatch", infoget(newgameinfo, "deathmatch"));
cvar_set("coop", infoget(newgameinfo, "coop"));
cvar_set("teamplay", infoget(newgameinfo, "teamplay"));
cvar_set("sv_public", infoget(newgameinfo, "sv_public"));
cvar_set("timelimit", infoget(newgameinfo, "timelimit"));
cvar_set("fraglimit", infoget(newgameinfo, "fraglimit"));
string map = infoget(newgameinfo, "map");
if (map == "")
{
if (infoget(newgameinfo, "deathmatch"))
map = sprintf("dm%g", floor(random(1, 6)));
else
map = "start";
}
if (engine == E_QSS)
{
if (stof(infoget(newgameinfo, "maxclients")) != cvar("maxplayers"))
localcmd(sprintf("\ndisconnect; maxplayers %S\n", infoget(newgameinfo, "maxclients")?:"0"));
}
else
cvar_set("maxclients", infoget(newgameinfo, "maxclients"));
//'map' acts slightly differently in different engines.
//qss: kicks everyone, resets all parms
//fte: just resets parms.
//whereas 'changelevel' works more predictably, but does mean you might get more weapons than were really intended.
if (engine == E_QSS && gametype == "warp")
localcmd(sprintf("\nchangelevel %S\n", map));
else
localcmd(sprintf("\nmap %S\n", map));
return;
}
if (newgameinfo)
strunzone(newgameinfo);
newgameinfo = "";
newgameinfo = infoadd(newgameinfo, "hostname", cvar_string("hostname"));
newgameinfo = infoadd(newgameinfo, "deathmatch", cvar_string("deathmatch"));
newgameinfo = infoadd(newgameinfo, "teamplay", cvar_string("teamplay"));
newgameinfo = infoadd(newgameinfo, "sv_public", cvar_string("sv_public"));
newgameinfo = infoadd(newgameinfo, "maxclients", cvar_string((engine == E_QSS)?"maxplayers":"maxclients"));
newgameinfo = infoadd(newgameinfo, "timelimit", cvar_string("timelimit"));
newgameinfo = infoadd(newgameinfo, "fraglimit", cvar_string("fraglimit"));
newgameinfo = strzone(newgameinfo);
//create the menu
m = spawn(mitem_newgame, item_text:_("New Game"), item_flags:IF_SELECTABLE, item_command:"m_main");
desktop.add(m, RS_X_MIN_PARENT_MIN|RS_Y_MIN_PARENT_MIN | RS_X_MAX_PARENT_MAX|RS_Y_MAX_PARENT_MAX, '0 0', '0 0');
desktop.item_focuschange(m, IF_KFOCUSED);
m.totop();
#if 1
mitem_frame fr = m;
#else
//create a frame for its scrollbar.
float h = 100;
mitem_frame fr = spawn(mitem_frame, item_flags: IF_SELECTABLE, frame_hasscroll:TRUE);
m.add(fr, RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MID | RS_X_MAX_PARENT_MID|RS_Y_MAX_OWN_MIN, [-160, -h], [160, h*2]);
#endif
switch(gametype)
{
case "tdm":
case "dm":
case "coop":
case "sp":
case "warp":
break;
default:
//show game type selection
pos = (16/-2)*(4);
banner = spawn(mitem_pic, item_text:"gfx/p_option.lmp", item_size_y:24, item_flags:IF_CENTERALIGN);
m.addm(banner, [(160-banner.item_size_x)*0.5, pos-32], [(160+banner.item_size_x)*0.5, pos-8]);
m.addm(spawn(mitem_text, item_text:"Single Player", item_command:"m_pop;m_newgame sp", item_scale:16, item_flags:IF_CENTERALIGN), [0, pos], [160, pos+16]); pos += 16;
m.addm(spawn(mitem_text, item_text:"Cooperative", item_command:"m_pop;m_newgame coop", item_scale:16, item_flags:IF_CENTERALIGN), [0, pos], [160, pos+16]); pos += 16;
m.addm(spawn(mitem_text, item_text:"Deathmatch", item_command:"m_pop;m_newgame dm", item_scale:16, item_flags:IF_CENTERALIGN), [0, pos], [160, pos+16]); pos += 16;
m.addm(spawn(mitem_text, item_text:"Team Deathmatch", item_command:"m_pop;m_newgame tdm", item_scale:16, item_flags:IF_CENTERALIGN), [0, pos], [160, pos+16]); pos += 16;
m.addm(spawn(mitem_text, item_text:"Map Selection", item_command:"m_pop;m_newgame warp", item_scale:16, item_flags:IF_CENTERALIGN), [0, pos], [160, pos+16]); pos += 16;
m.add(spawn (mitem_spinnymodel, item_text: "progs/soldier.mdl",firstframe:73, framecount:8, shootframe:81, shootframes:9), RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MID | RS_X_MAX_PARENT_MID|RS_Y_MAX_PARENT_MID, [-160, 12*-16/2], [0, 12*16/2]);
return;
}
pos = -100;
banner = spawn(mitem_pic, item_text:"gfx/p_multi.lmp", item_size_y:24, item_flags:IF_CENTERALIGN);
m.addm(banner, [(0-banner.item_size_x)*0.5, pos-32], [(0+banner.item_size_x)*0.5, pos-8]);
if (gametype != "warp")
{
fr.addm(menuitemeditt_spawn(_("Hostname"), "hostname", '280 8'), [-160, pos], [160, pos+8]); pos += 8;
fr.addm(menuitemcombo_spawn(_("Public"), "sv_public", '280 8', (engine == E_QSS)?_( "0 \"LAN\" "
"1 \"Internet\" "
):_( "-1 \"Reject All (Splitscreen)\" "
"0 \"Private (Manual IP Sharing)\" "
"1 \"Public (Manual Config)\" "
"2 \"Public (Holepunch)\" "
)), [-160, pos], [160, pos+8]); pos += 8;
fr.addm(menuitemcombo_spawn(_("Max Clients"), "maxclients", '280 8', _(
"2 \"Two\" "
"3 \"Three\" "
"4 \"Four\" "
"8 \"Eight\" "
"16 \"Sixteen\" "
"32 \"Thirty Two\" "
)), [-160, pos], [160, pos+8]); pos += 8;
}
if (gametype == "dm" || gametype == "tdm")
{
if (m.get("deathmatch") == "0")
{
m.set("deathmatch", "1");
m.set("coop", "0");
}
fr.addm(menuitemcombo_spawn(_("Deathmatch Mode"), "deathmatch", '280 8', _(
"1 \"Weapons Respawn\" "
"2 \"Weapons Stay\" "
"3 \"Powerups Respawn\" "
"4 \"Start With Weapons\" "
)), [-160, pos], [160, pos+8]); pos += 8;
}
else
{
if (m.get("coop") == "0")
{
m.set("deathmatch", "0");
m.set("coop", "1");
}
}
if (gametype == "tdm")
{
if (m.get("teamplay") == "0")
m.set("teamplay", "1");
}
if (gametype == "dm")
{
if (m.get("teamplay") != "0")
m.set("teamplay", "0");
}
if (gametype == "coop")
fr.addm(menuitemcheck_spawn(_("No Friendly Fire"), "teamplay", '280 8'), [-160, pos], [160, pos+8]), pos += 8;
// if (gametype == "dm" || gametype == "tdm")
if (gametype == "coop")
m.set("map", "start");
else
{
m.set("map", "");
if (gametype != "warp")
{
fr.addm(menuitemcombo_spawn(_("Time Limit"), "timelimit", '280 8', _(
"0 \"No Limit\" "
"5 \"5 minutes\" "
"10 \"10 minutes\" "
"15 \"15 minutes\" "
"20 \"20 minutes\" "
"25 \"25 minutes\" "
"30 \"30 minutes\" "
"35 \"35 minutes\" "
"40 \"40 minutes\" "
"45 \"45 minutes\" "
"50 \"50 minutes\" "
"55 \"55 minutes\" "
"60 \"1 hour\" "
)), [-160, pos], [160, pos+8]); pos += 8;
fr.addm(menuitemcombo_spawn(_("Frag Limit"), "fraglimit", '280 8', _(
"0 \"No Limit\" "
"10 \"10 frags\" "
"20 \"20 frags\" "
"30 \"30 frags\" "
"40 \"40 frags\" "
"50 \"50 frags\" "
"60 \"60 frags\" "
"70 \"70 frags\" "
"80 \"80 frags\" "
"90 \"90 frags\" "
"100 \"100 frags\" "
)), [-160, pos], [160, pos+8]); pos += 8;
}
pos += 8;
fr.addm(spawn(mitem_maplist, item_scale:8, item_flags:IF_CENTERALIGN|IF_SELECTABLE), [-160, pos], [150, 100-16-8]), pos = 100-16;
}
fr.addm(spawn(mitem_text, item_text:"BEGIN!", item_command:"m_pop;m_newgame begin", item_scale:16, item_flags:IF_CENTERALIGN), [-160, pos], [160, pos+16]);pos += 16;
//random art for style
if (gametype == "coop")
{
m.add(spawn (mitem_spinnymodel, item_text: "progs/soldier.mdl", firstframe:73, framecount:8, shootframe:81, shootframes:9), RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MID | RS_X_MAX_PARENT_MID|RS_Y_MAX_PARENT_MID, [-160, -240/2], [0, 240/2]);
}
else
{
m.add(spawn (mitem_spinnymodel, item_text: "progs/player.mdl", firstframe:0, framecount:6, shootframe:119, shootframes:6), RS_X_MIN_PARENT_MID|RS_Y_MIN_PARENT_MID | RS_X_MAX_PARENT_MID|RS_Y_MAX_PARENT_MID, [-160, 12*-16/2], [0, 12*16/2]);
}
addmenuback(m);
};