rip out most of gamecode

master
Daniel Kolesa 2020-04-16 23:18:05 +02:00
parent 69ad67a65e
commit b03c776b81
21 changed files with 91 additions and 6291 deletions

View File

@ -21,12 +21,6 @@ defaultsoundpack = [
registersound // pain2
registersound "uphys/die1" 150
registersound "uphys/die2" 150
registersound "ctf/flagpickup" 100
registersound "ctf/flagdrop" 100
registersound "ctf/flagreturn" 100
registersound "ctf/flagscore" 100
registersound "ctf/flagreturn" 100
registersound "ctf/flagfail" 100
]
loadsoundpack = [

View File

@ -3,7 +3,6 @@
exec "config/ui/lib.cfg" // UI library
exec "config/ui/style.cfg" // Styles
exec "config/ui/scoreboard.cfg" // Scoreboard
exec "config/ui/edithud.cfg" // Edit HUD
exec "config/ui/fkey.cfg" // F# Key Menus
exec "config/ui/serverbrowser.cfg" // Server Browser
@ -36,46 +35,6 @@ UImenu "main" [
uifill 0.2 0.02 [UIbar 1]
UIbutton "hold2" [uitext "Quit" 0.65] 0.2 0.04 [quit]
]
if (! $mainmenu) [
uifill 0.02 0.24 [UIbar 0 1]
uivlist 0 [
if (isspectator $getclientnum) [
if $scoreboardmultiplayer [
if (ismaster $getclientnum) [
UIbutton "hold2" [uitext "Play" 0.65] 0.2 0.04 [hideui "main" ; spectator 0]
] [
if (> $getmastermode 1) [
uifill 0 0.04 [uitext "^f4Play" 0.65]
] [
UIbutton "hold2" [uitext "Play" 0.65] 0.2 0.04 [hideui "main" ; spectator 0]
]
]
] [
UIbutton "hold2" [uitext "Play" 0.65] 0.2 0.04 [hideui "main" ; spectator 0]
]
] [
UIbutton "hold2" [uitext "Spectate" 0.65] 0.2 0.04 [hideui "main" ; spectator 1]
]
case $getteam [
0] [uifill 0 0.04] [
1] [UIbutton "hold2" [uitext "Join ^f3Rojo" 0.65] 0.2 0.04 [team rojo]] [
2] [UIbutton "hold2" [uitext "Join ^f1Azul" 0.65] 0.2 0.04 [team azul]]
uifill 0.2 0.02 [UIbar 1]
if $scoreboardmultiplayer [
UIbutton "hold2" [uitext "Master" 0.65] 0.2 0.04 [hideui "main" ; showui "master"]
if (ismaster $getclientnum) [
UIbutton "hold2" [uitext "Bots" 0.65] 0.2 0.04 [hideui "main" ; showui "bots"]
] [
uifill 0 0.04
]
] [
uifill 0 0.04
UIbutton "hold2" [uitext "Bots" 0.65] 0.2 0.04 [hideui "main" ; showui "bots"]
]
uifill 0.2 0.02 [UIbar 1]
UIbutton "hold2" [uitext "Disconnect" 0.65] 0.2 0.04 [disconnect]
]
]
]
]
@ -234,7 +193,7 @@ UImenu "credits" [
///////////////////////////////////////////////////////////////////////////////
// Master //
///////////////////////////////////////////////////////////////////////////////
menu_master = [if $isconnected [if $scoreboardmultiplayer [toggleui "master"]]]
menu_master = []
UImenu "master" [
uifill 0 0 [

View File

@ -57,7 +57,7 @@ newui "varicons" [
uialign 1 1
uispace 0.01 0.1 [
uivlist 0 [
looplist i (concatword "allfaces entselsnap entediting fullbright showmat " (? $scoreboardmultiplayer "nompedit")) [
looplist i (concatword "allfaces entselsnap entediting fullbright showmat") [
uifont "default_outline" [
uialign 1
if $$i [

View File

@ -1,222 +0,0 @@
///////////////////////////////////////////////////////////////////////////////
// Scoreboard //
///////////////////////////////////////////////////////////////////////////////
newui "scoreboard" [
if $mainmenu [hideui "scoreboard"]
uiallowinput 0
refreshscoreboard
uicolor (? $sbtransparent $c_menu_a $c_menu) 0 0 [
uivlist 0 [
UI_sbheader
uifill 0 0.005
if (> $getmode 2) UI_playertableteam UI_playertablesolo
UI_spectatorlist
]
]
] [if $mainmenu [hideui "scoreboard"]]
///////////////////////////////////////////////////////////////////////////////
UI_sbwsolo = 0.68
UI_sbwteam = 1.18
UI_sbheader = [
uifill 0 0.005
uifill (? (> $getmode 2) $UI_sbwteam $UI_sbwsolo) 0.05 [
uifill (-f (? (> $getmode 2) $UI_sbwteam $UI_sbwsolo) (? (= $showip 1) 0.18 0)) 0 [
uialign -1
uiclip (-f (? (> $getmode 2) $UI_sbwteam $UI_sbwsolo) (? (= $showip 1) 0.18 0)) 0 [
uialign -1
uispace 0.01 0 [
uivlist 0 [
if $scoreboardservinfo [
uitext $scoreboardservinfo 0.65
] [
uitext "^f4Tesseract" 0.65
]
uihlist 0.015 [
uifill
uihlist 0.003 [
uitext "^fs[" 0.52
uitext (concatword (at ["^f0" "^f0" "^f2" "^f3"] $getmastermode) (getmastermodename $getmastermode)) 0.52
uitext "^fS]" 0.52
]
UItriangle 0x606060 0.01 0.01 270
uitext (getmodeprettyname $getmode) 0.52
UItriangle 0x606060 0.01 0.01 270
uitext $scoreboardmap 0.52
if (m_timed $getmode) [
UItriangle 0x606060 0.01 0.01 270
uitext (concatword (? (|| $intermission $paused) "^f3" "^f8") $scoreboardtime) 0.52
]
]
uialign* -1
]
]
]
]
if $scoreboardmultiplayer [
if $showip [
uifill 0.18 0 [
uialign 1
UIbar 0 1; uialign- -1
uiclip 0.18 0 [
uialign 1
uispace 0.01 0 [
uivlist 0.004 [
uicolortext $connectedip 0xA0A0A0 0.52
uicolortext $connectedport 0xA0A0A0 0.52
uialign* 1
]
]
uialign- 1
]
]
]
]
]
]
//-------------------------------------------------------------------------------------------------
UI_cw_s = 0.042
UI_cw_k = 0.054
UI_cw_d = 0.054
UI_cw_pj = 0.050
UI_cw_p = 0.050
UI_cw_cn = 0.040
UI_cw_n = [-f (? (> $getmode 2) (*f $UI_sbwteam 0.5) $UI_sbwsolo) (? (> $getmode 2) (? $showscore $UI_cw_s)) (? $showkills $UI_cw_k) (? $showdeaths $UI_cw_d) (? $scoreboardmultiplayer (? $showpj $UI_cw_pj)) (? $scoreboardmultiplayer (? $showping $UI_cw_p)) (? $scoreboardmultiplayer (? $showclientnum $UI_cw_cn)) 0.03]
// ^ blarg
UI_playertablesolo = [
uifill 0 0 [
uitable 0 0 [
uitableheader [
uifill 0.015
uifill (UI_cw_n) 0.022
if $showkills [uifill $UI_cw_k 0.022 [uicolortext "K" 0xBBCC8B 0.45 ; uialign- 1 1]]
if $showdeaths [uifill $UI_cw_d 0.022 [uicolortext "D" 0xE56767 0.45 ; uialign- 1 1]]
if $scoreboardmultiplayer [
if $showpj [uifill $UI_cw_pj 0.022 [uicolortext "PJ" 0x77A1D9 0.45 ; uialign- 1 1]]
if $showping [uifill $UI_cw_p 0.022 [uicolortext "P" 0x77A1D9 0.45 ; uialign- 1 1]]
if $showclientnum [uifill $UI_cw_cn 0.022 [uicolortext "#" 0xA0A0A0 0.45 ; uialign- 1 1]]
]
uifill 0.015
] [uicolor 0x88161616 0 0 [uiclamp 1 1 1 1]]
UI_sbtc = 0
loopscoreboard cn 0 [
UI_sbtc = (! $UI_sbtc)
uitablerow [
uifill 0.015
uifill (UI_cw_n) 0.026 [uicolortext (getclientcolorname $cn) (scoreboardstatus $cn) 0.57 ; uialign- -1]
if $showkills [uifill $UI_cw_k 0.026 [uicolortext (getclientfrags $cn) 0xBBCC8B 0.52 ; uialign- 1]]
if $showdeaths [uifill $UI_cw_d 0.026 [uicolortext (getclientdeaths $cn) 0xE56767 0.52 ; uialign- 1]]
if $scoreboardmultiplayer [
if $showpj [uifill $UI_cw_pj 0.026 [uicolortext (? (isai $cn) "^f4-" (scoreboardpj $cn)) 0x77A1D9 0.52 ; uialign- 1]]
if $showping [uifill $UI_cw_p 0.026 [uicolortext (? (isai $cn) "^f4-" (scoreboardping $cn)) 0x77A1D9 0.52 ; uialign- 1]]
if $showclientnum [uifill $UI_cw_cn 0.026 [uicolortext (? (isai $cn) "^f4-" $cn) 0xA0A0A0 0.52 ; uialign- 1]]
]
uifill 0.015
] [
uicolor (? $UI_sbtc 0x99323232 0x99262626) 0 0 [uiclamp 1 1 1 1]
pushif highlight (scoreboardhighlight $cn) [uioutline 0xA0A0A0 (-f $UI_sbwsolo 0.002) 0.024]
]
]
]
]
]
UI_playertableteam = [
uifill 0 0 [
uihlist 0 [
uitable 0 0 [
uialign 0 -1
uitableheader [
uifill 0.015
if $scoreboardmultiplayer [
if $showclientnum [uifill $UI_cw_cn 0.042 [uicolortext "#" 0xA0A0A0 0.45 ; uialign- -1 1]]
if $showping [uifill $UI_cw_p 0.042 [uicolortext "P" 0x77A1D9 0.45 ; uialign- -1 1]]
if $showpj [uifill $UI_cw_pj 0.042 [uicolortext "PJ" 0x77A1D9 0.45 ; uialign- -1 1]]
]
if $showdeaths [uifill $UI_cw_d 0.042 [uicolortext "D" 0xE56767 0.45 ; uialign- -1 1]]
if $showkills [uifill $UI_cw_k 0.042 [uicolortext "K" 0xBBCC8B 0.45 ; uialign- -1 1]]
if $showscore [uifill $UI_cw_s 0.042 [uicolortext "Score" 0x62B370 0.45 ; uialign- -1 1]]
uifill (UI_cw_n) 0.042 [uicolortext (getteamscore 1) 0x4060D0 1 ; uialign- 1]
uifill 0.015
] [uicolor 0x99202860 0 0 [uiclamp 1 1 1 1]]
UI_sbtc = 0
loopscoreboard cn 1 [
UI_sbtc = (! $UI_sbtc)
uitablerow [
uifill 0.015
if $scoreboardmultiplayer [
if $showclientnum [uifill $UI_cw_cn 0.026 [uicolortext (? (isai $cn) "^f4-" $cn) 0xA0A0A0 0.52 ; uialign- -1]]
if $showping [uifill $UI_cw_p 0.026 [uicolortext (? (isai $cn) "^f4-" (scoreboardping $cn)) 0x77A1D9 0.52 ; uialign- -1]]
if $showpj [uifill $UI_cw_pj 0.026 [uicolortext (? (isai $cn) "^f4-" (scoreboardpj $cn)) 0x77A1D9 0.52 ; uialign- -1]]
]
if $showdeaths [uifill $UI_cw_d 0.026 [uicolortext (getclientdeaths $cn) 0xE56767 0.52 ; uialign- -1]]
if $showkills [uifill $UI_cw_k 0.026 [uicolortext (getclientfrags $cn) 0xBBCC8B 0.52 ; uialign- -1]]
if $showscore [uifill $UI_cw_s 0.026 [uicolortext (getclientflags $cn) 0x62B370 0.52 ; uialign- -1]]
uifill (UI_cw_n) 0.026 [uicolortext (getclientcolorname $cn) (scoreboardstatus $cn) 0.57 ; uialign- 1]
uifill 0.015
] [
uicolor (? $UI_sbtc 0x99333b40 0x99262b33) 0 0 [uiclamp 1 1 1 1]
pushif highlight (scoreboardhighlight $cn) [uioutline 0xA0A0A0 (-f (*f $UI_sbwteam 0.5) 0.002) 0.024]
]
]
]
uitable 0 0 [
uialign 0 -1
uitableheader [
uifill 0.015
uifill (UI_cw_n) 0.042 [uicolortext (getteamscore 2) 0xD04040 1 ; uialign- -1]
if $showscore [uifill $UI_cw_s 0.042 [uicolortext "Score" 0x62B370 0.45 ; uialign- 1 1]]
if $showkills [uifill $UI_cw_k 0.042 [uicolortext "K" 0xBBCC8B 0.45 ; uialign- 1 1]]
if $showdeaths [uifill $UI_cw_d 0.042 [uicolortext "D" 0xE56767 0.45 ; uialign- 1 1]]
if $scoreboardmultiplayer [
if $showpj [uifill $UI_cw_pj 0.042 [uicolortext "PJ" 0x77A1D9 0.45 ; uialign- 1 1]]
if $showping [uifill $UI_cw_p 0.042 [uicolortext "P" 0x77A1D9 0.45 ; uialign- 1 1]]
if $showclientnum [uifill $UI_cw_cn 0.042 [uicolortext "#" 0xA0A0A0 0.45 ; uialign- 1 1]]
]
uifill 0.015
] [uicolor 0x99602020 0 0 [uiclamp 1 1 1 1]]
UI_sbtc = 0
loopscoreboard cn 2 [
UI_sbtc = (! $UI_sbtc)
uitablerow [
uifill 0.015
uifill (UI_cw_n) 0.026 [uicolortext (getclientcolorname $cn) (scoreboardstatus $cn) 0.57 ; uialign- -1]
if $showscore [uifill $UI_cw_s 0.026 [uicolortext (getclientflags $cn) 0x62B370 0.52 ; uialign- 1]]
if $showkills [uifill $UI_cw_k 0.026 [uicolortext (getclientfrags $cn) 0xBBCC8B 0.52 ; uialign- 1]]
if $showdeaths [uifill $UI_cw_d 0.026 [uicolortext (getclientdeaths $cn) 0xE56767 0.52 ; uialign- 1]]
if $scoreboardmultiplayer [
if $showpj [uifill $UI_cw_pj 0.026 [uicolortext (? (isai $cn) "^f4-" (scoreboardpj $cn)) 0x77A1D9 0.52 ; uialign- 1]]
if $showping [uifill $UI_cw_p 0.026 [uicolortext (? (isai $cn) "^f4-" (scoreboardping $cn)) 0x77A1D9 0.52 ; uialign- 1]]
if $showclientnum [uifill $UI_cw_cn 0.026 [uicolortext (? (isai $cn) "^f4-" $cn) 0xA0A0A0 0.52 ; uialign- 1]]
]
uifill 0.015
] [
uicolor (? $UI_sbtc 0x99403333 0x99332626) 0 0 [uiclamp 1 1 1 1]
pushif highlight (scoreboardhighlight $cn) [uioutline 0xA0A0A0 (-f (*f $UI_sbwteam 0.5) 0.002) 0.024]
]
]
]
]
]
]
//-------------------------------------------------------------------------------------------------
UI_spectatorlist = [
uicolor 0x88161616 0 0.022 [
uiclamp 1 1 1 1
if $showspectators [
uigrid (? (> $getmode 2) 3 2) 0 0 [
loopscoreboard cn -1 [
uispace 0.01 0.01 [
pushif sbhigh (scoreboardhighlight $cn) [
uioutline 0xA0A0A0 ; uiclamp- 1 1 1 1
]
uihlist 0.01 [
uicolortext (getclientcolorname $cn) (scoreboardstatus $cn) 0.57
if $scoreboardmultiplayer [if $showclientnum [uitext $cn 0.57]]
]
]
]
]
]
]
]

View File

@ -40,14 +40,11 @@ client_src = [
'../engine/water.cc',
'../engine/world.cc',
'../engine/worldio.cc',
'../game/ai.cc',
'../game/client.cc',
'../game/entities.cc',
'../game/game.cc',
'../game/render.cc',
'../game/scoreboard.cc',
'../game/server.cc',
'../game/waypoint.cc',
'../game/weapon.cc'
]

View File

@ -1890,7 +1890,7 @@ bool moveplayer(physent *pl, int moveres, bool local, int curtime)
else if(pl->inwater && !water) game::physicstrigger(pl, local, 0, 1, pl->inwater);
pl->inwater = water ? material&MATF_VOLUME : MAT_AIR;
if(pl->state==CS_ALIVE && (pl->o.z < 0 || material&MAT_DEATH)) game::suicide(pl);
//if(pl->state==CS_ALIVE && (pl->o.z < 0 || material&MAT_DEATH)) game::suicide(pl);
return true;
}

View File

@ -194,7 +194,6 @@ void sendpacket(int n, int chan, ENetPacket *packet, int exclude)
{
if(n<0)
{
server::recordpacket(chan, packet->data, packet->dataLength);
loopv(clients) if(i!=exclude && server::allowbroadcast(i)) sendpacket(i, chan, packet);
return;
}

File diff suppressed because it is too large Load Diff

View File

@ -1,317 +0,0 @@
struct gameent;
#define MAXBOTS 32
enum { AI_NONE = 0, AI_BOT, AI_MAX };
#define isaitype(a) (a >= 0 && a <= AI_MAX-1)
namespace ai
{
const int MAXWAYPOINTS = USHRT_MAX - 2;
const int MAXWAYPOINTLINKS = 6;
const int WAYPOINTRADIUS = 16;
const float MINWPDIST = 4.f; // is on top of
const float CLOSEDIST = 32.f; // is close
const float FARDIST = 128.f; // too far to remap close
const float JUMPMIN = 4.f; // decides to jump
const float JUMPMAX = 32.f; // max jump
const float SIGHTMIN = 64.f; // minimum line of sight
const float SIGHTMAX = 1024.f; // maximum line of sight
const float VIEWMIN = 90.f; // minimum field of view
const float VIEWMAX = 180.f; // maximum field of view
struct waypoint
{
vec o;
float curscore, estscore;
int weight;
ushort route, prev;
ushort links[MAXWAYPOINTLINKS];
waypoint() {}
waypoint(const vec &o, int weight = 0) : o(o), weight(weight), route(0) { memset(links, 0, sizeof(links)); }
int score() const { return int(curscore) + int(estscore); }
int find(int wp)
{
loopi(MAXWAYPOINTLINKS) if(links[i] == wp) return i;
return -1;
}
bool haslinks() { return links[0]!=0; }
};
extern vector<waypoint> waypoints;
static inline bool iswaypoint(int n)
{
return n > 0 && n < waypoints.length();
}
extern int showwaypoints, dropwaypoints;
extern int closestwaypoint(const vec &pos, float mindist, bool links, gameent *d = NULL);
extern void findwaypointswithin(const vec &pos, float mindist, float maxdist, vector<int> &results);
extern void inferwaypoints(gameent *d, const vec &o, const vec &v, float mindist = ai::CLOSEDIST);
struct avoidset
{
struct obstacle
{
void *owner;
int numwaypoints;
float above;
obstacle(void *owner, float above = -1) : owner(owner), numwaypoints(0), above(above) {}
};
vector<obstacle> obstacles;
vector<int> waypoints;
void clear()
{
obstacles.setsize(0);
waypoints.setsize(0);
}
void add(void *owner, float above)
{
obstacles.add(obstacle(owner, above));
}
void add(void *owner, float above, int wp)
{
if(obstacles.empty() || owner != obstacles.last().owner) add(owner, above);
obstacles.last().numwaypoints++;
waypoints.add(wp);
}
void add(avoidset &avoid)
{
waypoints.put(avoid.waypoints.getbuf(), avoid.waypoints.length());
loopv(avoid.obstacles)
{
obstacle &o = avoid.obstacles[i];
if(obstacles.empty() || o.owner != obstacles.last().owner) add(o.owner, o.above);
obstacles.last().numwaypoints += o.numwaypoints;
}
}
void avoidnear(void *owner, float above, const vec &pos, float limit);
#define loopavoid(v, d, body) \
if(!(v).obstacles.empty()) \
{ \
int cur = 0; \
loopv((v).obstacles) \
{ \
const ai::avoidset::obstacle &ob = (v).obstacles[i]; \
int next = cur + ob.numwaypoints; \
if(ob.owner != d) \
{ \
for(; cur < next; cur++) \
{ \
int wp = (v).waypoints[cur]; \
body; \
} \
} \
cur = next; \
} \
}
bool find(int n, gameent *d) const
{
loopavoid(*this, d, { if(wp == n) return true; });
return false;
}
int remap(gameent *d, int n, vec &pos, bool retry = false);
};
extern bool route(gameent *d, int node, int goal, vector<int> &route, const avoidset &obstacles, int retries = 0);
extern void navigate();
extern void clearwaypoints(bool full = false);
extern void seedwaypoints();
extern void loadwaypoints(bool force = false, const char *mname = NULL);
extern void savewaypoints(bool force = false, const char *mname = NULL);
// ai state information for the owner client
enum
{
AI_S_WAIT = 0, // waiting for next command
AI_S_DEFEND, // defend goal target
AI_S_PURSUE, // pursue goal target
AI_S_INTEREST, // interest in goal entity
AI_S_MAX
};
enum
{
AI_T_NODE,
AI_T_PLAYER,
AI_T_AFFINITY,
AI_T_ENTITY,
AI_T_MAX
};
struct interest
{
int state, node, target, targtype;
float score;
interest() : state(-1), node(-1), target(-1), targtype(-1), score(0.f) {}
~interest() {}
};
struct aistate
{
int type, millis, targtype, target, idle;
bool override;
aistate(int m, int t, int r = -1, int v = -1) : type(t), millis(m), targtype(r), target(v)
{
reset();
}
~aistate() {}
void reset()
{
idle = 0;
override = false;
}
};
const int NUMPREVNODES = 6;
struct aiinfo
{
vector<aistate> state;
vector<int> route;
vec target, spot;
int enemy, enemyseen, enemymillis, weappref, prevnodes[NUMPREVNODES], targnode, targlast, targtime, targseq,
lastrun, lasthunt, lastaction, lastcheck, jumpseed, jumprand, blocktime, huntseq, blockseq, lastaimrnd;
float targyaw, targpitch, views[3], aimrnd[3];
bool dontmove, becareful, tryreset, trywipe;
aiinfo()
{
clearsetup();
reset();
loopk(3) views[k] = 0.f;
}
~aiinfo() {}
void clearsetup()
{
weappref = GUN_RAIL;
spot = target = vec(0, 0, 0);
lastaction = lasthunt = lastcheck = enemyseen = enemymillis = blocktime = huntseq = blockseq = targtime = targseq = lastaimrnd = 0;
lastrun = jumpseed = lastmillis;
jumprand = lastmillis+5000;
targnode = targlast = enemy = -1;
}
void clear(bool prev = false)
{
if(prev) memset(prevnodes, -1, sizeof(prevnodes));
route.setsize(0);
}
void wipe(bool prev = false)
{
clear(prev);
state.setsize(0);
addstate(AI_S_WAIT);
trywipe = false;
}
void clean(bool tryit = false)
{
if(!tryit) becareful = dontmove = false;
targyaw = rnd(360);
targpitch = 0.f;
tryreset = tryit;
}
void reset(bool tryit = false) { wipe(); clean(tryit); }
bool hasprevnode(int n) const
{
loopi(NUMPREVNODES) if(prevnodes[i] == n) return true;
return false;
}
void addprevnode(int n)
{
if(prevnodes[0] != n)
{
memmove(&prevnodes[1], prevnodes, sizeof(prevnodes) - sizeof(prevnodes[0]));
prevnodes[0] = n;
}
}
aistate &addstate(int t, int r = -1, int v = -1)
{
return state.add(aistate(lastmillis, t, r, v));
}
void removestate(int index = -1)
{
if(index < 0) state.pop();
else if(state.inrange(index)) state.remove(index);
if(!state.length()) addstate(AI_S_WAIT);
}
aistate &getstate(int idx = -1)
{
if(state.inrange(idx)) return state[idx];
return state.last();
}
aistate &switchstate(aistate &b, int t, int r = -1, int v = -1)
{
if((b.type == t && b.targtype == r) || (b.type == AI_S_INTEREST && b.targtype == AI_T_NODE))
{
b.millis = lastmillis;
b.target = v;
b.reset();
return b;
}
return addstate(t, r, v);
}
};
extern avoidset obstacles;
extern vec aitarget;
extern float viewdist(int x = 101);
extern float viewfieldx(int x = 101);
extern float viewfieldy(int x = 101);
extern bool targetable(gameent *d, gameent *e);
extern bool cansee(gameent *d, vec &x, vec &y, vec &targ = aitarget);
extern void init(gameent *d, int at, int on, int sk, int bn, int pm, int col, const char *name, int team);
extern void update();
extern void avoid();
extern void think(gameent *d, bool run);
extern bool badhealth(gameent *d);
extern bool checkothers(vector<int> &targets, gameent *d = NULL, int state = -1, int targtype = -1, int target = -1, bool teams = false, int *members = NULL);
extern bool makeroute(gameent *d, aistate &b, int node, bool changed = true, int retries = 0);
extern bool makeroute(gameent *d, aistate &b, const vec &pos, bool changed = true, int retries = 0);
extern bool randomnode(gameent *d, aistate &b, const vec &pos, float guard = SIGHTMIN, float wander = SIGHTMAX);
extern bool randomnode(gameent *d, aistate &b, float guard = SIGHTMIN, float wander = SIGHTMAX);
extern bool violence(gameent *d, aistate &b, gameent *e, int pursue = 0);
extern bool patrol(gameent *d, aistate &b, const vec &pos, float guard = SIGHTMIN, float wander = SIGHTMAX, int walk = 1, bool retry = false);
extern bool defend(gameent *d, aistate &b, const vec &pos, float guard = SIGHTMIN, float wander = SIGHTMAX, int walk = 1);
extern void assist(gameent *d, aistate &b, vector<interest> &interests, bool all = false, bool force = false);
extern bool parseinterests(gameent *d, aistate &b, vector<interest> &interests, bool override = false, bool ignore = false);
extern void spawned(gameent *d);
extern void damaged(gameent *d, gameent *e);
extern void killed(gameent *d, gameent *e);
extern void itemspawned(int ent);
extern void render();
}

View File

@ -1,276 +0,0 @@
// server-side ai manager
namespace aiman
{
bool dorefresh = false, botbalance = true;
VARN(serverbotlimit, botlimit, 0, 8, MAXBOTS);
VAR(serverbotbalance, 0, 1, 1);
void calcteams(vector<teamscore> &teams)
{
loopv(clients)
{
clientinfo *ci = clients[i];
if(ci->state.state==CS_SPECTATOR || !validteam(ci->team)) continue;
teamscore *t = NULL;
loopvj(teams) if(teams[j].team == ci->team) { t = &teams[j]; break; }
if(t) t->score++;
else teams.add(teamscore(ci->team, 1));
}
teams.sort(teamscore::compare);
if(teams.length() < MAXTEAMS)
{
loopi(MAXTEAMS) if(teams.htfind(1+i) < 0) teams.add(teamscore(1+i, 0));
}
}
void balanceteams()
{
vector<teamscore> teams;
calcteams(teams);
vector<clientinfo *> reassign;
loopv(bots) if(bots[i]) reassign.add(bots[i]);
while(reassign.length() && teams.length() && teams[0].score > teams.last().score + 1)
{
teamscore &t = teams.last();
clientinfo *bot = NULL;
loopv(reassign) if(reassign[i] && reassign[i]->team != teams[0].team)
{
bot = reassign.removeunordered(i);
teams[0].score--;
t.score++;
for(int j = teams.length() - 2; j >= 0; j--)
{
if(teams[j].score >= teams[j+1].score) break;
swap(teams[j], teams[j+1]);
}
break;
}
if(bot)
{
if(smode && bot->state.state==CS_ALIVE) smode->changeteam(bot, bot->team, t.team);
bot->team = t.team;
sendf(-1, 1, "riiii", N_SETTEAM, bot->clientnum, bot->team, 0);
}
else teams.remove(0, 1);
}
}
int chooseteam()
{
vector<teamscore> teams;
calcteams(teams);
return teams.length() ? teams.last().team : 0;
}
static inline bool validaiclient(clientinfo *ci)
{
return ci->clientnum >= 0 && ci->state.aitype == AI_NONE && (ci->state.state!=CS_SPECTATOR || ci->local || (ci->privilege && !ci->warned));
}
clientinfo *findaiclient(clientinfo *exclude = NULL)
{
clientinfo *least = NULL;
loopv(clients)
{
clientinfo *ci = clients[i];
if(!validaiclient(ci) || ci==exclude) continue;
if(!least || ci->bots.length() < least->bots.length()) least = ci;
}
return least;
}
bool addai(int skill, int limit)
{
int numai = 0, cn = -1, maxai = limit >= 0 ? min(limit, MAXBOTS) : MAXBOTS;
loopv(bots)
{
clientinfo *ci = bots[i];
if(!ci || ci->ownernum < 0) { if(cn < 0) cn = i; continue; }
numai++;
}
if(numai >= maxai) return false;
if(bots.inrange(cn))
{
clientinfo *ci = bots[cn];
if(ci)
{ // reuse a slot that was going to removed
clientinfo *owner = findaiclient();
ci->ownernum = owner ? owner->clientnum : -1;
if(owner) owner->bots.add(ci);
ci->aireinit = 2;
dorefresh = true;
return true;
}
}
else { cn = bots.length(); bots.add(NULL); }
int team = m_teammode ? chooseteam() : 0;
if(!bots[cn]) bots[cn] = new clientinfo;
clientinfo *ci = bots[cn];
ci->clientnum = MAXCLIENTS + cn;
ci->state.aitype = AI_BOT;
clientinfo *owner = findaiclient();
ci->ownernum = owner ? owner->clientnum : -1;
if(owner) owner->bots.add(ci);
ci->state.skill = skill <= 0 ? rnd(50) + 51 : clamp(skill, 1, 101);
clients.add(ci);
ci->state.lasttimeplayed = lastmillis;
copystring(ci->name, "bot", MAXNAMELEN+1);
ci->state.state = CS_DEAD;
ci->team = team;
ci->playermodel = rnd(128);
ci->playercolor = rnd(0x8000);
ci->aireinit = 2;
ci->connected = true;
dorefresh = true;
return true;
}
void deleteai(clientinfo *ci)
{
int cn = ci->clientnum - MAXCLIENTS;
if(!bots.inrange(cn)) return;
if(ci->ownernum >= 0 && !ci->aireinit && smode) smode->leavegame(ci, true);
sendf(-1, 1, "ri2", N_CDIS, ci->clientnum);
clientinfo *owner = (clientinfo *)getclientinfo(ci->ownernum);
if(owner) owner->bots.removeobj(ci);
clients.removeobj(ci);
DELETEP(bots[cn]);
dorefresh = true;
}
bool deleteai()
{
loopvrev(bots) if(bots[i] && bots[i]->ownernum >= 0)
{
deleteai(bots[i]);
return true;
}
return false;
}
void reinitai(clientinfo *ci)
{
if(ci->ownernum < 0) deleteai(ci);
else if(ci->aireinit >= 1)
{
sendf(-1, 1, "ri8s", N_INITAI, ci->clientnum, ci->ownernum, ci->state.aitype, ci->state.skill, ci->playermodel, ci->playercolor, ci->team, ci->name);
if(ci->aireinit == 2)
{
ci->reassign();
if(ci->state.state==CS_ALIVE) sendspawn(ci);
else sendresume(ci);
}
ci->aireinit = 0;
}
}
void shiftai(clientinfo *ci, clientinfo *owner = NULL)
{
if(ci->ownernum >= 0 && !ci->aireinit && smode) smode->leavegame(ci, true);
clientinfo *prevowner = (clientinfo *)getclientinfo(ci->ownernum);
if(prevowner) prevowner->bots.removeobj(ci);
if(!owner) { ci->aireinit = 0; ci->ownernum = -1; }
else if(ci->ownernum != owner->clientnum) { ci->aireinit = 2; ci->ownernum = owner->clientnum; owner->bots.add(ci); }
dorefresh = true;
}
void removeai(clientinfo *ci)
{ // either schedules a removal, or someone else to assign to
loopvrev(ci->bots) shiftai(ci->bots[i], findaiclient(ci));
}
bool reassignai()
{
clientinfo *hi = NULL, *lo = NULL;
loopv(clients)
{
clientinfo *ci = clients[i];
if(!validaiclient(ci)) continue;
if(!lo || ci->bots.length() < lo->bots.length()) lo = ci;
if(!hi || ci->bots.length() > hi->bots.length()) hi = ci;
}
if(hi && lo && hi->bots.length() - lo->bots.length() > 1)
{
loopvrev(hi->bots)
{
shiftai(hi->bots[i], lo);
return true;
}
}
return false;
}
void checksetup()
{
if(m_teammode && botbalance) balanceteams();
loopvrev(bots) if(bots[i]) reinitai(bots[i]);
}
void clearai()
{ // clear and remove all ai immediately
loopvrev(bots) if(bots[i]) deleteai(bots[i]);
}
void checkai()
{
if(!dorefresh) return;
dorefresh = false;
if(m_botmode && numclients(-1, false, true))
{
checksetup();
while(reassignai());
}
else clearai();
}
void reqadd(clientinfo *ci, int skill)
{
if(!ci->local && !ci->privilege) return;
if(!addai(skill, !ci->local && ci->privilege < PRIV_ADMIN ? botlimit : -1)) sendf(ci->clientnum, 1, "ris", N_SERVMSG, "failed to create or assign bot");
}
void reqdel(clientinfo *ci)
{
if(!ci->local && !ci->privilege) return;
if(!deleteai()) sendf(ci->clientnum, 1, "ris", N_SERVMSG, "failed to remove any bots");
}
void setbotlimit(clientinfo *ci, int limit)
{
if(ci && !ci->local && ci->privilege < PRIV_ADMIN) return;
botlimit = clamp(limit, 0, MAXBOTS);
dorefresh = true;
defformatstring(msg, "bot limit is now %d", botlimit);
sendservmsg(msg);
}
void setbotbalance(clientinfo *ci, bool balance)
{
if(ci && !ci->local && !ci->privilege) return;
botbalance = balance ? 1 : 0;
dorefresh = true;
defformatstring(msg, "bot team balancing is now %s", botbalance ? "enabled" : "disabled");
sendservmsg(msg);
}
void changemap()
{
dorefresh = true;
loopv(clients) if(clients[i]->local || clients[i]->privilege) return;
if(botbalance != (serverbotbalance != 0)) setbotbalance(NULL, serverbotbalance != 0);
}
void addclient(clientinfo *ci)
{
if(ci->state.aitype == AI_NONE) dorefresh = true;
}
void changeteam(clientinfo *ci)
{
if(ci->state.aitype == AI_NONE) dorefresh = true;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -225,7 +225,6 @@ namespace entities
else d->vel = vec(0, 0, 0);
entinmap(d);
updatedynentcache(d);
ai::inferwaypoints(d, ents[n]->o, ents[e]->o, 16.f);
break;
}
}
@ -267,7 +266,6 @@ namespace entities
d->lastpickup = e->type;
d->lastpickupmillis = lastmillis;
jumppadeffects(d, n, true);
if(d->ai) d->ai->becareful = true;
d->falling = vec(0, 0, 0);
d->physstate = PHYS_FALL;
d->timeinair = 1;

View File

@ -1,145 +0,0 @@
#define EXT_ACK -1
#define EXT_VERSION 105
#define EXT_NO_ERROR 0
#define EXT_ERROR 1
#define EXT_PLAYERSTATS_RESP_IDS -10
#define EXT_PLAYERSTATS_RESP_STATS -11
#define EXT_UPTIME 0
#define EXT_PLAYERSTATS 1
#define EXT_TEAMSCORE 2
/*
Client:
-----
A: 0 EXT_UPTIME
B: 0 EXT_PLAYERSTATS cn #a client number or -1 for all players#
C: 0 EXT_TEAMSCORE
Server:
--------
A: 0 EXT_UPTIME EXT_ACK EXT_VERSION uptime #in seconds#
B: 0 EXT_PLAYERSTATS cn #send by client# EXT_ACK EXT_VERSION 0 or 1 #error, if cn was > -1 and client does not exist# ...
EXT_PLAYERSTATS_RESP_IDS pid(s) #1 packet#
EXT_PLAYERSTATS_RESP_STATS pid playerdata #1 packet for each player#
C: 0 EXT_TEAMSCORE EXT_ACK EXT_VERSION 0 or 1 #error, no teammode# remaining_time gamemode loop(teamdata [numbases bases] or -1)
Errors:
--------------
B:C:default: 0 command EXT_ACK EXT_VERSION EXT_ERROR
*/
VAR(extinfoip, 0, 0, 1);
void extinfoplayer(ucharbuf &p, clientinfo *ci)
{
ucharbuf q = p;
putint(q, EXT_PLAYERSTATS_RESP_STATS); // send player stats following
putint(q, ci->clientnum); //add player id
putint(q, ci->ping);
sendstring(ci->name, q);
sendstring(teamname(m_teammode ? ci->team : 0), q);
putint(q, ci->state.frags);
putint(q, ci->state.flags);
putint(q, ci->state.deaths);
putint(q, ci->state.teamkills);
putint(q, ci->state.damage*100/max(ci->state.shotdamage,1));
putint(q, ci->state.health);
putint(q, 0);
putint(q, ci->state.gunselect);
putint(q, ci->privilege);
putint(q, ci->state.state);
uint ip = extinfoip ? getclientip(ci->clientnum) : 0;
q.put((uchar*)&ip, 3);
sendserverinforeply(q);
}
static inline void extinfoteamscore(ucharbuf &p, int team, int score)
{
sendstring(teamname(team), p);
putint(p, score);
if(!smode || !smode->extinfoteam(team, p))
putint(p,-1); //no bases follow
}
void extinfoteams(ucharbuf &p)
{
putint(p, m_teammode ? 0 : 1);
putint(p, gamemode);
putint(p, max((gamelimit - gamemillis)/1000, 0));
if(!m_teammode) return;
vector<teamscore> scores;
if(smode && smode->hidefrags()) smode->getteamscores(scores);
loopv(clients)
{
clientinfo *ci = clients[i];
if(ci->state.state!=CS_SPECTATOR && validteam(ci->team) && scores.htfind(ci->team) < 0)
{
if(smode && smode->hidefrags()) scores.add(teamscore(ci->team, 0));
else { teaminfo &t = teaminfos[ci->team-1]; scores.add(teamscore(ci->team, t.frags)); }
}
}
loopv(scores) extinfoteamscore(p, scores[i].team, scores[i].score);
}
void extserverinforeply(ucharbuf &req, ucharbuf &p)
{
int extcmd = getint(req); // extended commands
//Build a new packet
putint(p, EXT_ACK); //send ack
putint(p, EXT_VERSION); //send version of extended info
switch(extcmd)
{
case EXT_UPTIME:
{
putint(p, totalsecs); //in seconds
break;
}
case EXT_PLAYERSTATS:
{
int cn = getint(req); //a special player, -1 for all
clientinfo *ci = NULL;
if(cn >= 0)
{
loopv(clients) if(clients[i]->clientnum == cn) { ci = clients[i]; break; }
if(!ci)
{
putint(p, EXT_ERROR); //client requested by id was not found
sendserverinforeply(p);
return;
}
}
putint(p, EXT_NO_ERROR); //so far no error can happen anymore
ucharbuf q = p; //remember buffer position
putint(q, EXT_PLAYERSTATS_RESP_IDS); //send player ids following
if(ci) putint(q, ci->clientnum);
else loopv(clients) putint(q, clients[i]->clientnum);
sendserverinforeply(q);
if(ci) extinfoplayer(p, ci);
else loopv(clients) extinfoplayer(p, clients[i]);
return;
}
case EXT_TEAMSCORE:
{
extinfoteams(p);
break;
}
default:
{
putint(p, EXT_ERROR);
break;
}
}
sendserverinforeply(p);
}

View File

@ -39,15 +39,6 @@ namespace game
void follow(char *arg)
{
int cn = -1;
if(arg[0])
{
if(player1->state != CS_SPECTATOR) return;
cn = parseplayer(arg);
if(cn == player1->clientnum) cn = -1;
}
if(cn < 0 && (following < 0 || specmode)) return;
following = cn;
}
COMMAND(follow, "s");
@ -96,25 +87,16 @@ namespace game
gameent *spawnstate(gameent *d) // reset player state not persistent accross spawns
{
d->respawn();
d->spawnstate(gamemode);
d->spawnstate(0);
return d;
}
void respawnself()
{
if(ispaused()) return;
if(m_mp(gamemode))
{
int seq = (player1->lifesequence<<16)|((lastmillis/1000)&0xFFFF);
if(player1->respawned!=seq) { addmsg(N_TRYSPAWN, "rc", player1); player1->respawned = seq; }
}
else
{
spawnplayer(player1);
showscores(false);
lasthit = 0;
if(cmode) cmode->respawned(player1);
}
spawnplayer(player1);
lasthit = 0;
if(cmode) cmode->respawned(player1);
}
gameent *pointatplayer()
@ -194,7 +176,7 @@ namespace game
loopv(players)
{
gameent *d = players[i];
if(d == player1 || d->ai) continue;
if(d == player1) continue;
if(d->state==CS_DEAD && d->ragdoll) moveragdoll(d);
else if(!intermission)
@ -225,10 +207,8 @@ namespace game
if(!curtime) { gets2c(); if(player1->clientnum>=0) c2sinfo(); return; }
physicsframe();
ai::navigate();
updateweapons(curtime);
otherplayers(curtime);
ai::update();
moveragdolls();
gets2c();
if(connected)
@ -354,15 +334,11 @@ namespace game
}
damageeffect(damage, d, d!=h);
ai::damaged(d, actor);
if(d->health<=0) { if(local) killed(d, actor); }
else if(d==h) playsound(S_PAIN2);
else playsound(S_PAIN1, &d->o);
}
VARP(deathscore, 0, 1, 1);
void deathstate(gameent *d, bool restore)
{
d->state = CS_DEAD;
@ -374,7 +350,6 @@ namespace game
}
if(d==player1)
{
if(deathscore) showscores(true);
disablezoom();
d->attacking = ACT_IDLE;
//d->pitch = 0;
@ -431,7 +406,6 @@ namespace game
else conoutf(contype, "\f2%s fragged %s", aname, dname);
}
deathstate(d);
ai::killed(d, actor);
}
void timeupdate(int secs)
@ -451,7 +425,6 @@ namespace game
int accuracy = (player1->totaldamage*100)/max(player1->totalshots, 1);
conoutf(CON_GAMEINFO, "\f2player total damage dealt: %d, damage wasted: %d, accuracy(%%): %d", player1->totaldamage, player1->totalshots-player1->totaldamage, accuracy);
showscores(true);
disablezoom();
execident("intermission");
@ -469,7 +442,7 @@ namespace game
gameent *newclient(int cn) // ensure valid entity
{
if(cn < 0 || cn > max(0xFF, MAXCLIENTS + MAXBOTS))
if(cn < 0 || cn > max(0xFF, MAXCLIENTS))
{
neterr("clientnum", false);
return NULL;
@ -497,7 +470,6 @@ namespace game
void clientdisconnected(int cn, bool notify)
{
if(!clients.inrange(cn)) return;
unignore(cn);
gameent *d = clients[cn];
if(d)
{
@ -506,7 +478,6 @@ namespace game
removetrackedparticles(d);
removetrackeddynlights(d);
if(cmode) cmode->removeplayer(d);
removegroupedplayer(d);
players.removeobj(d);
DELETEP(clients[cn]);
cleardynentcache();
@ -538,8 +509,6 @@ namespace game
clearbouncers();
clearragdolls();
clearteaminfo();
// reset perma-state
loopv(players) players[i]->startgame();
@ -555,14 +524,8 @@ namespace game
cmode->setup();
}
conoutf(CON_GAMEINFO, "\f2game mode is %s", server::modeprettyname(gamemode));
const char *info = m_valid(gamemode) ? gamemodes[gamemode - STARTGAMEMODE].info : NULL;
if(showmodeinfo && info) conoutf(CON_GAMEINFO, "\f0%s", info);
syncplayer();
showscores(false);
disablezoom();
lasthit = 0;
@ -571,11 +534,7 @@ namespace game
void startmap(const char *name) // called just after a map load
{
ai::savewaypoints();
ai::clearwaypoints(true);
if(!m_mp(gamemode)) spawnplayer(player1);
else findplayerspawn(player1, -1, m_teammode ? player1->team : 0);
spawnplayer(player1);
entities::resetspawns();
copystring(clientmap, name ? name : "");
@ -584,20 +543,20 @@ namespace game
const char *getmapinfo()
{
return showmodeinfo && m_valid(gamemode) ? gamemodes[gamemode - STARTGAMEMODE].info : NULL;
return NULL;
}
const char *getscreenshotinfo()
{
return server::modename(gamemode, NULL);
return NULL;
}
void physicstrigger(physent *d, bool local, int floorlevel, int waterlevel, int material)
{
if (waterlevel>0) { if(material!=MAT_LAVA) playsound(S_SPLASHOUT, d==player1 ? NULL : &d->o); }
else if(waterlevel<0) playsound(material==MAT_LAVA ? S_BURN : S_SPLASHIN, d==player1 ? NULL : &d->o);
if (floorlevel>0) { if(d==player1 || d->type!=ENT_PLAYER || ((gameent *)d)->ai) msgsound(S_JUMP, d); }
else if(floorlevel<0) { if(d==player1 || d->type!=ENT_PLAYER || ((gameent *)d)->ai) msgsound(S_LAND, d); }
if (floorlevel>0) { if(d==player1 || d->type!=ENT_PLAYER) msgsound(S_JUMP, d); }
else if(floorlevel<0) { if(d==player1 || d->type!=ENT_PLAYER) msgsound(S_LAND, d); }
}
void dynentcollide(physent *d, physent *o, const vec &dir)
@ -613,8 +572,6 @@ namespace game
}
else
{
if(d->type==ENT_PLAYER && ((gameent *)d)->ai)
addmsg(N_SOUND, "ci", d, n);
playsound(n, &d->o);
}
}
@ -638,10 +595,10 @@ namespace game
const char *colorname(gameent *d, const char *name, const char * alt, const char *color)
{
if(!name) name = alt && d == player1 ? alt : d->name;
bool dup = !name[0] || duplicatename(d, name, alt) || d->aitype != AI_NONE;
bool dup = !name[0] || duplicatename(d, name, alt);
if(dup || color[0])
{
if(dup) return tempformatstring(d->aitype == AI_NONE ? "\fs%s%s \f5(%d)\fr" : "\fs%s%s \f5[%d]\fr", color, name, d->clientnum);
if(dup) return tempformatstring("\fs%s%s \f5(%d)\fr", color, name, d->clientnum);
return tempformatstring("\fs%s%s\fr", color, name);
}
return name;
@ -673,22 +630,6 @@ namespace game
teamsound(isteam(d->team, player1->team), n, loc);
}
void suicide(physent *d)
{
if(d==player1 || (d->type==ENT_PLAYER && ((gameent *)d)->ai))
{
if(d->state!=CS_ALIVE) return;
gameent *pl = (gameent *)d;
if(!m_mp(gamemode)) killed(pl, pl);
else
{
int seq = (pl->lifesequence<<16)|((lastmillis/1000)&0xFFFF);
if(pl->suicided!=seq) { addmsg(N_SUICIDE, "rc", pl); pl->suicided = seq; }
}
}
}
ICOMMAND(suicide, "", (), suicide(player1));
bool needminimap() { return false; }
void drawicon(int icon, float x, float y, float sz)
@ -719,53 +660,12 @@ namespace game
void drawhudicons(gameent *d)
{
#if 0
pushhudscale(2);
draw_textf("%d", (HICON_X + HICON_SIZE + HICON_SPACE)/2, HICON_TEXTY/2, d->state==CS_DEAD ? 0 : d->health);
if(d->state!=CS_DEAD)
{
draw_textf("%d", (HICON_X + 2*HICON_STEP + HICON_SIZE + HICON_SPACE)/2, HICON_TEXTY/2, d->ammo[d->gunselect]);
}
pophudmatrix();
resethudshader();
drawicon(HICON_HEALTH, HICON_X, HICON_Y);
if(d->state!=CS_DEAD)
{
drawicon(HICON_MELEE+d->gunselect, HICON_X + 2*HICON_STEP, HICON_Y);
}
#endif
}
void gameplayhud(int w, int h)
{
pushhudscale(h/1800.0f);
if(player1->state==CS_SPECTATOR)
{
float pw, ph, tw, th, fw, fh;
text_boundsf(" ", pw, ph);
text_boundsf("SPECTATOR", tw, th);
th = max(th, ph);
gameent *f = followingplayer();
text_boundsf(f ? colorname(f) : " ", fw, fh);
fh = max(fh, ph);
draw_text("SPECTATOR", w*1800/h - tw - pw, 1650 - th - fh);
if(f)
{
int color = f->state!=CS_DEAD ? 0xFFFFFF : 0x606060;
if(f->privilege)
{
color = f->privilege>=PRIV_ADMIN ? 0xFF8000 : 0x40FF80;
if(f->state==CS_DEAD) color = (color>>1)&0x7F7F7F;
}
draw_text(colorname(f), w*1800/h - fw - pw, 1650 - fh, (color>>16)&0xFF, (color>>8)&0xFF, color&0xFF);
}
resethudshader();
}
gameent *d = hudplayer();
if(d->state!=CS_EDITING)
{
@ -795,80 +695,11 @@ namespace game
}
}
int selectcrosshair(vec &col)
int selectcrosshair(vec &)
{
gameent *d = hudplayer();
if(d->state==CS_SPECTATOR || d->state==CS_DEAD || UI::uivisible("scoreboard")) return -1;
if(d->state!=CS_ALIVE) return 0;
int crosshair = 0;
if(lasthit && lastmillis - lasthit < hitcrosshair) crosshair = 2;
else if(teamcrosshair && m_teammode)
{
dynent *o = intersectclosest(d->o, worldpos, d);
if(o && o->type==ENT_PLAYER && validteam(d->team) && ((gameent *)o)->team == d->team)
{
crosshair = 1;
col = vec::hexcolor(teamtextcolor[d->team]);
}
}
#if 0
if(crosshair!=1 && !editmode)
{
if(d->health<=25) { r = 1.0f; g = b = 0; }
else if(d->health<=50) { r = 1.0f; g = 0.5f; b = 0; }
}
#endif
if(d->gunwait) col.mul(0.5f);
return crosshair;
return 0;
}
const char *mastermodecolor(int n, const char *unknown)
{
return (n>=MM_START && size_t(n-MM_START)<sizeof(mastermodecolors)/sizeof(mastermodecolors[0])) ? mastermodecolors[n-MM_START] : unknown;
}
const char *mastermodeicon(int n, const char *unknown)
{
return (n>=MM_START && size_t(n-MM_START)<sizeof(mastermodeicons)/sizeof(mastermodeicons[0])) ? mastermodeicons[n-MM_START] : unknown;
}
ICOMMAND(servinfomode, "i", (int *i), GETSERVINFOATTR(*i, 0, mode, intret(mode)));
ICOMMAND(servinfomodename, "i", (int *i),
GETSERVINFOATTR(*i, 0, mode,
{
const char *name = server::modeprettyname(mode, NULL);
if(name) result(name);
}));
ICOMMAND(servinfomastermode, "i", (int *i), GETSERVINFOATTR(*i, 2, mm, intret(mm)));
ICOMMAND(servinfomastermodename, "i", (int *i),
GETSERVINFOATTR(*i, 2, mm,
{
const char *name = server::mastermodename(mm, NULL);
if(name) stringret(newconcatstring(mastermodecolor(mm, ""), name));
}));
ICOMMAND(servinfotime, "ii", (int *i, int *raw),
GETSERVINFOATTR(*i, 1, secs,
{
secs = clamp(secs, 0, 59*60+59);
if(*raw) intret(secs);
else
{
int mins = secs/60;
secs %= 60;
result(tempformatstring("%d:%02d", mins, secs));
}
}));
ICOMMAND(servinfoicon, "i", (int *i),
GETSERVINFO(*i, si,
{
int mm = si->attr.inrange(2) ? si->attr[2] : MM_INVALID;
result(si->maxplayers > 0 && si->numplayers >= si->maxplayers ? "serverfull" : mastermodeicon(mm, "serverunk"));
}));
// any data written into this vector will get saved with the map data. Must take care to do own versioning, and endianess if applicable. Will not get called when loading maps from other games, so provide defaults.
void writegamedata(vector<char> &extras) {}
void readgamedata(vector<char> &extras) {}

View File

@ -124,18 +124,18 @@ static struct gamemodeinfo
#define m_checknot(mode, flag) (m_valid(mode) && !(gamemodes[(mode) - STARTGAMEMODE].flags&(flag)))
#define m_checkall(mode, flag) (m_valid(mode) && (gamemodes[(mode) - STARTGAMEMODE].flags&(flag)) == (flag))
#define m_teammode (m_check(gamemode, M_TEAM))
#define m_overtime (m_check(gamemode, M_OVERTIME))
#define isteam(a,b) (m_teammode && a==b)
#define m_rail (m_check(gamemode, M_RAIL))
#define m_pulse (m_check(gamemode, M_PULSE))
#define m_teammode false
#define m_overtime false
#define isteam(a,b) false
#define m_rail false
#define m_pulse false
#define m_demo (m_check(gamemode, M_DEMO))
#define m_edit (m_check(gamemode, M_EDIT))
#define m_lobby (m_check(gamemode, M_LOBBY))
#define m_timed (m_checknot(gamemode, M_DEMO|M_EDIT|M_LOCAL))
#define m_botmode (m_checknot(gamemode, M_DEMO|M_LOCAL))
#define m_mp(mode) (m_checknot(mode, M_LOCAL))
#define m_demo false
#define m_edit true
#define m_lobby false
#define m_timed false
#define m_botmode false
#define m_mp(mode) false
enum { MM_AUTH = -1, MM_OPEN = 0, MM_VETO, MM_LOCKED, MM_PRIVATE, MM_PASSWORD, MM_START = MM_AUTH, MM_INVALID = MM_START - 1 };
@ -151,14 +151,7 @@ enum
S_ITEMSPAWN, S_TELEPORT, S_JUMPPAD,
S_MELEE, S_PULSE1, S_PULSE2, S_PULSEEXPLODE, S_RAIL1, S_RAIL2,
S_WEAPLOAD, S_NOAMMO, S_HIT,
S_PAIN1, S_PAIN2, S_DIE1, S_DIE2,
S_FLAGPICKUP,
S_FLAGDROP,
S_FLAGRETURN,
S_FLAGSCORE,
S_FLAGRESET,
S_FLAGFAIL
S_PAIN1, S_PAIN2, S_DIE1, S_DIE2
};
// network messages codes, c2s, c2c, s2c
@ -177,19 +170,13 @@ enum
N_TIMEUP, N_FORCEINTERMISSION,
N_SERVMSG, N_ITEMLIST, N_RESUME,
N_EDITMODE, N_EDITENT, N_EDITF, N_EDITT, N_EDITM, N_FLIP, N_COPY, N_PASTE, N_ROTATE, N_REPLACE, N_DELCUBE, N_CALCLIGHT, N_REMIP, N_EDITVSLOT, N_UNDO, N_REDO, N_NEWMAP, N_GETMAP, N_SENDMAP, N_CLIPBOARD, N_EDITVAR,
N_MASTERMODE, N_KICK, N_CLEARBANS, N_CURRENTMASTER, N_SPECTATOR, N_SETMASTER, N_SETTEAM,
N_LISTDEMOS, N_SENDDEMOLIST, N_GETDEMO, N_SENDDEMO,
N_DEMOPLAYBACK, N_RECORDDEMO, N_STOPDEMO, N_CLEARDEMOS,
N_TAKEFLAG, N_RETURNFLAG, N_RESETFLAG, N_TRYDROPFLAG, N_DROPFLAG, N_SCOREFLAG, N_INITFLAGS,
N_SPECTATOR, N_SETTEAM,
N_SAYTEAM,
N_CLIENT,
N_AUTHTRY, N_AUTHKICK, N_AUTHCHAL, N_AUTHANS, N_REQAUTH,
N_PAUSEGAME, N_GAMESPEED,
N_ADDBOT, N_DELBOT, N_INITAI, N_FROMAI, N_BOTLIMIT, N_BOTBALANCE,
N_MAPCRC, N_CHECKMAPS,
N_SWITCHNAME, N_SWITCHMODEL, N_SWITCHCOLOR, N_SWITCHTEAM,
N_SERVCMD,
N_DEMOPACKET,
NUMMSG
};
@ -205,19 +192,13 @@ static const int msgsizes[] = // size inclusive message token, 0 f
N_TIMEUP, 2, N_FORCEINTERMISSION, 1,
N_SERVMSG, 0, N_ITEMLIST, 0, N_RESUME, 0,
N_EDITMODE, 2, N_EDITENT, 11, N_EDITF, 16, N_EDITT, 16, N_EDITM, 16, N_FLIP, 14, N_COPY, 14, N_PASTE, 14, N_ROTATE, 15, N_REPLACE, 17, N_DELCUBE, 14, N_CALCLIGHT, 1, N_REMIP, 1, N_EDITVSLOT, 16, N_UNDO, 0, N_REDO, 0, N_NEWMAP, 2, N_GETMAP, 1, N_SENDMAP, 0, N_EDITVAR, 0,
N_MASTERMODE, 2, N_KICK, 0, N_CLEARBANS, 1, N_CURRENTMASTER, 0, N_SPECTATOR, 3, N_SETMASTER, 0, N_SETTEAM, 0,
N_LISTDEMOS, 1, N_SENDDEMOLIST, 0, N_GETDEMO, 2, N_SENDDEMO, 0,
N_DEMOPLAYBACK, 3, N_RECORDDEMO, 2, N_STOPDEMO, 1, N_CLEARDEMOS, 2,
N_TAKEFLAG, 3, N_RETURNFLAG, 4, N_RESETFLAG, 3, N_TRYDROPFLAG, 1, N_DROPFLAG, 7, N_SCOREFLAG, 9, N_INITFLAGS, 0,
N_SPECTATOR, 3, N_SETTEAM, 0,
N_SAYTEAM, 0,
N_CLIENT, 0,
N_AUTHTRY, 0, N_AUTHKICK, 0, N_AUTHCHAL, 0, N_AUTHANS, 0, N_REQAUTH, 0,
N_PAUSEGAME, 0, N_GAMESPEED, 0,
N_ADDBOT, 2, N_DELBOT, 1, N_INITAI, 0, N_FROMAI, 2, N_BOTLIMIT, 2, N_BOTBALANCE, 2,
N_MAPCRC, 0, N_CHECKMAPS, 1,
N_SWITCHNAME, 0, N_SWITCHMODEL, 2, N_SWITCHCOLOR, 2, N_SWITCHTEAM, 2,
N_SERVCMD, 0,
N_DEMOPACKET, 0,
-1
};
@ -276,17 +257,14 @@ static const struct guninfo { const char *name, *file, *vwep; int attacks[NUMACT
{ "pulse rifle", "pulserifle", "worldgun/pulserifle", { -1, ATK_PULSE_SHOOT, ATK_PULSE_MELEE } }
};
#include "ai.hh"
// inherited by gameent and server clients
struct gamestate
{
int health, maxhealth;
int gunselect, gunwait;
int ammo[NUMGUNS];
int aitype, skill;
gamestate() : maxhealth(1), aitype(AI_NONE), skill(0) {}
gamestate() : maxhealth(1) {}
bool canpickup(int type)
{
@ -341,7 +319,6 @@ struct gamestate
static const char * const teamnames[1+MAXTEAMS] = { "", "azul", "rojo" };
static const char * const teamtextcode[1+MAXTEAMS] = { "\f0", "\f1", "\f3" };
static const int teamtextcolor[1+MAXTEAMS] = { 0x1EC850, 0x6496FF, 0xFF4B19 };
static const int teamscoreboardcolor[1+MAXTEAMS] = { 0, 0x3030C0, 0xC03030 };
static const char * const teamblipcolor[1+MAXTEAMS] = { "_neutral", "_blue", "_red" };
static inline int teamnumber(const char *name) { loopi(MAXTEAMS) if(!strcmp(teamnames[1+i], name)) return 1+i; return 0; }
#define validteam(n) ((n) >= 1 && (n) <= MAXTEAMS)
@ -350,7 +327,7 @@ static inline int teamnumber(const char *name) { loopi(MAXTEAMS) if(!strcmp(team
struct gameent : dynent, gamestate
{
int weight; // affects the effectiveness of hitpush
int clientnum, privilege, lastupdate, plag, ping;
int clientnum, lastupdate, plag, ping;
int lifesequence; // sequence id for each respawn, used in damage test
int respawned, suicided;
int lastpain;
@ -365,12 +342,11 @@ struct gameent : dynent, gamestate
string name, info;
int team, playermodel, playercolor;
ai::aiinfo *ai;
int ownernum, lastnode;
vec muzzle;
gameent() : weight(100), clientnum(-1), privilege(PRIV_NONE), lastupdate(0), plag(0), ping(0), lifesequence(0), respawned(-1), suicided(-1), lastpain(0), frags(0), flags(0), deaths(0), totaldamage(0), totalshots(0), edit(NULL), smoothmillis(-1), team(0), playermodel(-1), playercolor(0), ai(NULL), ownernum(-1), muzzle(-1, -1, -1)
gameent() : weight(100), clientnum(-1), lastupdate(0), plag(0), ping(0), lifesequence(0), respawned(-1), suicided(-1), lastpain(0), frags(0), flags(0), deaths(0), totaldamage(0), totalshots(0), edit(NULL), smoothmillis(-1), team(0), playermodel(-1), playercolor(0), ownernum(-1), muzzle(-1, -1, -1)
{
name[0] = info[0] = 0;
respawn();
@ -378,7 +354,6 @@ struct gameent : dynent, gamestate
~gameent()
{
freeeditinfo(edit);
if(ai) delete ai;
}
void hitpush(int damage, const vec &dir, gameent *actor, int atk)
@ -418,23 +393,6 @@ struct gameent : dynent, gamestate
}
};
struct teamscore
{
int team, score;
teamscore() {}
teamscore(int team, int n) : team(team), score(n) {}
static bool compare(const teamscore &x, const teamscore &y)
{
if(x.score > y.score) return true;
if(x.score < y.score) return false;
return x.team < y.team;
}
};
static inline uint hthash(const teamscore &t) { return hthash(t.team); }
static inline bool htcmp(int team, const teamscore &t) { return team == t.team; }
struct teaminfo
{
int frags;
@ -467,8 +425,6 @@ namespace entities
namespace game
{
extern int gamemode;
struct clientmode
{
virtual ~clientmode() {}
@ -486,19 +442,12 @@ namespace game
virtual void removeplayer(gameent *d) {}
virtual void gameover() {}
virtual bool hidefrags() { return false; }
virtual int getteamscore(int team) { return 0; }
virtual void getteamscores(vector<teamscore> &scores) {}
virtual void aifind(gameent *d, ai::aistate &b, vector<ai::interest> &interests) {}
virtual bool aicheck(gameent *d, ai::aistate &b) { return false; }
virtual bool aidefend(gameent *d, ai::aistate &b) { return false; }
virtual bool aipursue(gameent *d, ai::aistate &b) { return false; }
};
extern clientmode *cmode;
extern void setclientmode();
// game
extern int nextmode;
extern string clientmap;
extern bool intermission;
extern int maptime, maprealtime, maplimit;
@ -542,16 +491,12 @@ namespace game
extern vector<uchar> messages;
extern int parseplayer(const char *arg);
extern void ignore(int cn);
extern void unignore(int cn);
extern bool isignored(int cn);
extern bool addmsg(int type, const char *fmt = NULL, ...);
extern void switchname(const char *name);
extern void switchteam(const char *name);
extern void switchplayermodel(int playermodel);
extern void switchplayercolor(int playercolor);
extern void sendmapinfo();
extern void stopdemo();
extern void changemap(const char *name, int mode);
extern void c2sinfo(bool force = false);
extern void sendposition(gameent *d, bool reliable = false);
@ -580,15 +525,6 @@ namespace game
extern void updateweapons(int curtime);
extern void gunselect(int gun, gameent *d);
extern void weaponswitch(gameent *d);
extern void avoidweapons(ai::avoidset &obstacles, float radius);
// scoreboard
extern void showscores(bool on);
extern void getbestplayers(vector<gameent *> &best);
extern void getbestteams(vector<int> &best);
extern void clearteaminfo();
extern void setteaminfo(int team, int frags);
extern void removegroupedplayer(gameent *d);
// render
struct playermodelinfo

View File

@ -21,7 +21,6 @@ namespace game
gameent *r = new gameent(*d);
r->lastupdate = ragdollfade && lastmillis > d->lastpain + max(ragdollmillis - ragdollfade, 0) ? lastmillis - max(ragdollmillis - ragdollfade, 0) : d->lastpain;
r->edit = NULL;
r->ai = NULL;
if(d==player1) r->playermodel = playermodel;
ragdolls.add(r);
d->ragdoll = NULL;
@ -318,14 +317,10 @@ namespace game
void rendergame()
{
ai::render();
if(intermission)
{
bestteams.shrink(0);
bestplayers.shrink(0);
if(m_teammode) getbestteams(bestteams);
else getbestplayers(bestplayers);
}
bool third = isthirdperson();

View File

@ -1,216 +0,0 @@
// creation of scoreboard
#include "game.hh"
namespace game
{
VARP(showservinfo, 0, 1, 1);
VARP(showclientnum, 0, 0, 1);
VARP(showpj, 0, 0, 1);
VARP(showping, 0, 1, 1);
VARP(showspectators, 0, 1, 1);
VARP(highlightscore, 0, 1, 1);
VARP(showconnecting, 0, 0, 1);
VARP(hidefrags, 0, 1, 1);
static teaminfo teaminfos[MAXTEAMS];
void clearteaminfo()
{
loopi(MAXTEAMS) teaminfos[i].reset();
}
void setteaminfo(int team, int frags)
{
if(!validteam(team)) return;
teaminfo &t = teaminfos[team-1];
t.frags = frags;
}
static inline bool playersort(const gameent *a, const gameent *b)
{
if(a->state==CS_SPECTATOR)
{
if(b->state==CS_SPECTATOR) return strcmp(a->name, b->name) < 0;
else return false;
}
else if(b->state==CS_SPECTATOR) return true;
if(a->frags > b->frags) return true;
if(a->frags < b->frags) return false;
return strcmp(a->name, b->name) < 0;
}
void getbestplayers(vector<gameent *> &best)
{
loopv(players)
{
gameent *o = players[i];
if(o->state!=CS_SPECTATOR) best.add(o);
}
best.sort(playersort);
while(best.length() > 1 && best.last()->frags < best[0]->frags) best.drop();
}
void getbestteams(vector<int> &best)
{
if(cmode && cmode->hidefrags())
{
vector<teamscore> teamscores;
cmode->getteamscores(teamscores);
teamscores.sort(teamscore::compare);
while(teamscores.length() > 1 && teamscores.last().score < teamscores[0].score) teamscores.drop();
loopv(teamscores) best.add(teamscores[i].team);
}
else
{
int bestfrags = INT_MIN;
loopi(MAXTEAMS)
{
teaminfo &t = teaminfos[i];
bestfrags = max(bestfrags, t.frags);
}
loopi(MAXTEAMS)
{
teaminfo &t = teaminfos[i];
if(t.frags >= bestfrags) best.add(1+i);
}
}
}
static vector<gameent *> teamplayers[1+MAXTEAMS], spectators;
static void groupplayers()
{
loopi(1+MAXTEAMS) teamplayers[i].setsize(0);
spectators.setsize(0);
loopv(players)
{
gameent *o = players[i];
if(!showconnecting && !o->name[0]) continue;
if(o->state==CS_SPECTATOR) { spectators.add(o); continue; }
int team = m_teammode && validteam(o->team) ? o->team : 0;
teamplayers[team].add(o);
}
loopi(1+MAXTEAMS) teamplayers[i].sort(playersort);
spectators.sort(playersort);
}
void removegroupedplayer(gameent *d)
{
loopi(1+MAXTEAMS) teamplayers[i].removeobj(d);
spectators.removeobj(d);
}
void refreshscoreboard()
{
groupplayers();
}
COMMAND(refreshscoreboard, "");
ICOMMAND(numscoreboard, "i", (int *team), intret(*team < 0 ? spectators.length() : (*team <= MAXTEAMS ? teamplayers[*team].length() : 0)));
ICOMMAND(loopscoreboard, "rie", (ident *id, int *team, uint *body),
{
if(*team > MAXTEAMS) return;
loopstart(id, stack);
vector<gameent *> &p = *team < 0 ? spectators : teamplayers[*team];
loopv(p)
{
loopiter(id, stack, p[i]->clientnum);
execute(body);
}
loopend(id, stack);
});
ICOMMAND(scoreboardstatus, "i", (int *cn),
{
gameent *d = getclient(*cn);
if(d)
{
int status = d->state!=CS_DEAD ? 0xFFFFFF : 0x606060;
if(d->privilege)
{
status = d->privilege>=PRIV_ADMIN ? 0xFF8000 : 0x40FF80;
if(d->state==CS_DEAD) status = (status>>1)&0x7F7F7F;
}
intret(status);
}
});
ICOMMAND(scoreboardpj, "i", (int *cn),
{
gameent *d = getclient(*cn);
if(d && d != player1)
{
if(d->state==CS_LAGGED) result("LAG");
else intret(d->plag);
}
});
ICOMMAND(scoreboardping, "i", (int *cn),
{
gameent *d = getclient(*cn);
if(d)
{
if(!showpj && d->state==CS_LAGGED) result("LAG");
else intret(d->ping);
}
});
ICOMMAND(scoreboardshowfrags, "", (), intret(cmode && cmode->hidefrags() && hidefrags ? 0 : 1));
ICOMMAND(scoreboardshowclientnum, "", (), intret(showclientnum || player1->privilege>=PRIV_MASTER ? 1 : 0));
ICOMMAND(scoreboardmultiplayer, "", (), intret(multiplayer(false) || demoplayback ? 1 : 0));
ICOMMAND(scoreboardhighlight, "i", (int *cn),
intret(*cn == player1->clientnum && highlightscore && (multiplayer(false) || demoplayback || players.length() > 1) ? 0x808080 : 0));
ICOMMAND(scoreboardservinfo, "", (),
{
if(!showservinfo) return;
const ENetAddress *address = connectedpeer();
if(address && player1->clientnum >= 0)
{
if(servdesc[0]) result(servdesc);
else
{
string hostname;
if(enet_address_get_host_ip(address, hostname, sizeof(hostname)) >= 0)
result(tempformatstring("%s:%d", hostname, address->port));
}
}
});
ICOMMAND(scoreboardmode, "", (),
{
result(server::modeprettyname(gamemode));
});
ICOMMAND(scoreboardmap, "", (),
{
const char *mname = getclientmap();
result(mname[0] ? mname : "[new map]");
});
ICOMMAND(scoreboardtime, "", (),
{
if(m_timed && getclientmap() && (maplimit >= 0 || intermission))
{
if(intermission) result("intermission");
else
{
int secs = max(maplimit-lastmillis + 999, 0)/1000;
result(tempformatstring("%d:%02d", secs/60, secs%60));
}
}
});
ICOMMAND(getteamscore, "i", (int *team),
{
if(m_teammode && validteam(*team))
{
if(cmode && cmode->hidefrags()) intret(cmode->getteamscore(*team));
else intret(teaminfos[*team-1].frags);
}
});
void showscores(bool on) { UI::holdui("scoreboard", on); }
}

File diff suppressed because it is too large Load Diff

View File

@ -1,806 +0,0 @@
#include "game.hh"
extern selinfo sel;
namespace ai
{
using namespace game;
vector<waypoint> waypoints;
bool clipped(const vec &o)
{
int material = lookupmaterial(o), clipmat = material&MATF_CLIP;
return clipmat == MAT_CLIP || material&MAT_DEATH || (material&MATF_VOLUME) == MAT_LAVA;
}
int getweight(const vec &o)
{
vec pos = o; pos.z += ai::JUMPMIN;
if(!insideworld(vec(pos.x, pos.y, min(pos.z, getworldsize() - 1e-3f)))) return -2;
float dist = raycube(pos, vec(0, 0, -1), 0, RAY_CLIPMAT);
int posmat = lookupmaterial(pos), weight = 1;
if(isliquid(posmat&MATF_VOLUME)) weight *= 5;
if(dist >= 0)
{
weight = int(dist/ai::JUMPMIN);
pos.z -= clamp(dist-8.0f, 0.0f, pos.z);
int trgmat = lookupmaterial(pos);
if(trgmat&MAT_DEATH || (trgmat&MATF_VOLUME) == MAT_LAVA) weight *= 10;
else if(isliquid(trgmat&MATF_VOLUME)) weight *= 2;
}
return weight;
}
enum
{
WPCACHE_STATIC = 0,
WPCACHE_DYNAMIC,
NUMWPCACHES
};
struct wpcache
{
struct node
{
float split[2];
uint child[2];
int axis() const { return child[0]>>30; }
int childindex(int which) const { return child[which]&0x3FFFFFFF; }
bool isleaf(int which) const { return (child[1]&(1<<(30+which)))!=0; }
};
vector<node> nodes;
int firstwp, lastwp;
vec bbmin, bbmax;
wpcache() { clear(); }
void clear()
{
nodes.setsize(0);
firstwp = lastwp = -1;
bbmin = vec(1e16f, 1e16f, 1e16f);
bbmax = vec(-1e16f, -1e16f, -1e16f);
}
void build(int first = 0, int last = -1)
{
if(last < 0) last = waypoints.length();
vector<int> indices;
for(int i = first; i < last; i++)
{
waypoint &w = waypoints[i];
indices.add(i);
if(firstwp < 0) firstwp = i;
float radius = WAYPOINTRADIUS;
bbmin.min(vec(w.o).sub(radius));
bbmax.max(vec(w.o).add(radius));
}
if(first < last) lastwp = max(lastwp, last-1);
if(indices.length())
{
nodes.reserve(indices.length());
build(indices.getbuf(), indices.length(), bbmin, bbmax);
}
}
void build(int *indices, int numindices, const vec &vmin, const vec &vmax)
{
int axis = 2;
loopk(2) if(vmax[k] - vmin[k] > vmax[axis] - vmin[axis]) axis = k;
vec leftmin(1e16f, 1e16f, 1e16f), leftmax(-1e16f, -1e16f, -1e16f), rightmin(1e16f, 1e16f, 1e16f), rightmax(-1e16f, -1e16f, -1e16f);
float split = 0.5f*(vmax[axis] + vmin[axis]), splitleft = -1e16f, splitright = 1e16f;
int left, right;
for(left = 0, right = numindices; left < right;)
{
waypoint &w = waypoints[indices[left]];
float radius = WAYPOINTRADIUS;
if(max(split - (w.o[axis]-radius), 0.0f) > max((w.o[axis]+radius) - split, 0.0f))
{
++left;
splitleft = max(splitleft, w.o[axis]+radius);
leftmin.min(vec(w.o).sub(radius));
leftmax.max(vec(w.o).add(radius));
}
else
{
--right;
swap(indices[left], indices[right]);
splitright = min(splitright, w.o[axis]-radius);
rightmin.min(vec(w.o).sub(radius));
rightmax.max(vec(w.o).add(radius));
}
}
if(!left || right==numindices)
{
leftmin = rightmin = vec(1e16f, 1e16f, 1e16f);
leftmax = rightmax = vec(-1e16f, -1e16f, -1e16f);
left = right = numindices/2;
splitleft = -1e16f;
splitright = 1e16f;
loopi(numindices)
{
waypoint &w = waypoints[indices[i]];
float radius = WAYPOINTRADIUS;
if(i < left)
{
splitleft = max(splitleft, w.o[axis]+radius);
leftmin.min(vec(w.o).sub(radius));
leftmax.max(vec(w.o).add(radius));
}
else
{
splitright = min(splitright, w.o[axis]-radius);
rightmin.min(vec(w.o).sub(radius));
rightmax.max(vec(w.o).add(radius));
}
}
}
int offset = nodes.length();
node &curnode = nodes.add();
curnode.split[0] = splitleft;
curnode.split[1] = splitright;
if(left<=1) curnode.child[0] = (axis<<30) | (left>0 ? indices[0] : 0x3FFFFFFF);
else
{
curnode.child[0] = (axis<<30) | (nodes.length()-offset);
if(left) build(indices, left, leftmin, leftmax);
}
if(numindices-right<=1) curnode.child[1] = (1<<31) | (left<=1 ? 1<<30 : 0) | (numindices-right>0 ? indices[right] : 0x3FFFFFFF);
else
{
curnode.child[1] = (left<=1 ? 1<<30 : 0) | (nodes.length()-offset);
if(numindices-right) build(&indices[right], numindices-right, rightmin, rightmax);
}
}
} wpcaches[NUMWPCACHES];
static int invalidatedwpcaches = 0, clearedwpcaches = (1<<NUMWPCACHES)-1, numinvalidatewpcaches = 0, lastwpcache = 0;
static inline void invalidatewpcache(int wp)
{
if(++numinvalidatewpcaches >= 1000) { numinvalidatewpcaches = 0; invalidatedwpcaches = (1<<NUMWPCACHES)-1; }
else
{
loopi(WPCACHE_DYNAMIC) if(wp >= wpcaches[i].firstwp && wp <= wpcaches[i].lastwp) { invalidatedwpcaches |= 1<<i; return; }
invalidatedwpcaches |= 1<<WPCACHE_DYNAMIC;
}
}
void clearwpcache(bool full = true)
{
loopi(NUMWPCACHES) if(full || invalidatedwpcaches&(1<<i)) { wpcaches[i].clear(); clearedwpcaches |= 1<<i; }
if(full || invalidatedwpcaches == (1<<NUMWPCACHES)-1)
{
numinvalidatewpcaches = 0;
lastwpcache = 0;
}
invalidatedwpcaches = 0;
}
ICOMMAND(clearwpcache, "", (), clearwpcache());
avoidset wpavoid;
void buildwpcache()
{
loopi(NUMWPCACHES) if(wpcaches[i].firstwp < 0)
wpcaches[i].build(i > 0 ? wpcaches[i-1].lastwp+1 : 1, i+1 >= NUMWPCACHES || wpcaches[i+1].firstwp < 0 ? -1 : wpcaches[i+1].firstwp);
clearedwpcaches = 0;
lastwpcache = waypoints.length();
wpavoid.clear();
loopv(waypoints) if(waypoints[i].weight < 0) wpavoid.avoidnear(NULL, waypoints[i].o.z + WAYPOINTRADIUS, waypoints[i].o, WAYPOINTRADIUS);
}
struct wpcachestack
{
wpcache::node *node;
float tmin, tmax;
};
vector<wpcache::node *> wpcachestack;
int closestwaypoint(const vec &pos, float mindist, bool links, gameent *d)
{
if(waypoints.empty()) return -1;
if(clearedwpcaches) buildwpcache();
#define CHECKCLOSEST(index) do { \
int n = (index); \
if(n < waypoints.length()) \
{ \
const waypoint &w = waypoints[n]; \
if(!links || w.links[0]) \
{ \
float dist = w.o.squaredist(pos); \
if(dist < mindist*mindist) { closest = n; mindist = sqrtf(dist); } \
} \
} \
} while(0)
int closest = -1;
wpcache::node *curnode;
loop(which, NUMWPCACHES) if(wpcaches[which].firstwp >= 0) for(curnode = &wpcaches[which].nodes[0], wpcachestack.setsize(0);;)
{
int axis = curnode->axis();
float dist1 = pos[axis] - curnode->split[0], dist2 = curnode->split[1] - pos[axis];
if(dist1 >= mindist)
{
if(dist2 < mindist)
{
if(!curnode->isleaf(1)) { curnode += curnode->childindex(1); continue; }
CHECKCLOSEST(curnode->childindex(1));
}
}
else if(curnode->isleaf(0))
{
CHECKCLOSEST(curnode->childindex(0));
if(dist2 < mindist)
{
if(!curnode->isleaf(1)) { curnode += curnode->childindex(1); continue; }
CHECKCLOSEST(curnode->childindex(1));
}
}
else
{
if(dist2 < mindist)
{
if(!curnode->isleaf(1)) wpcachestack.add(curnode + curnode->childindex(1));
else CHECKCLOSEST(curnode->childindex(1));
}
curnode += curnode->childindex(0);
continue;
}
if(wpcachestack.empty()) break;
curnode = wpcachestack.pop();
}
for(int i = lastwpcache; i < waypoints.length(); i++) { CHECKCLOSEST(i); }
return closest;
}
void findwaypointswithin(const vec &pos, float mindist, float maxdist, vector<int> &results)
{
if(waypoints.empty()) return;
if(clearedwpcaches) buildwpcache();
float mindist2 = mindist*mindist, maxdist2 = maxdist*maxdist;
#define CHECKWITHIN(index) do { \
int n = (index); \
if(n < waypoints.length()) \
{ \
const waypoint &w = waypoints[n]; \
float dist = w.o.squaredist(pos); \
if(dist > mindist2 && dist < maxdist2) results.add(n); \
} \
} while(0)
wpcache::node *curnode;
loop(which, NUMWPCACHES) if(wpcaches[which].firstwp >= 0) for(curnode = &wpcaches[which].nodes[0], wpcachestack.setsize(0);;)
{
int axis = curnode->axis();
float dist1 = pos[axis] - curnode->split[0], dist2 = curnode->split[1] - pos[axis];
if(dist1 >= maxdist)
{
if(dist2 < maxdist)
{
if(!curnode->isleaf(1)) { curnode += curnode->childindex(1); continue; }
CHECKWITHIN(curnode->childindex(1));
}
}
else if(curnode->isleaf(0))
{
CHECKWITHIN(curnode->childindex(0));
if(dist2 < maxdist)
{
if(!curnode->isleaf(1)) { curnode += curnode->childindex(1); continue; }
CHECKWITHIN(curnode->childindex(1));
}
}
else
{
if(dist2 < maxdist)
{
if(!curnode->isleaf(1)) wpcachestack.add(curnode + curnode->childindex(1));
else CHECKWITHIN(curnode->childindex(1));
}
curnode += curnode->childindex(0);
continue;
}
if(wpcachestack.empty()) break;
curnode = wpcachestack.pop();
}
for(int i = lastwpcache; i < waypoints.length(); i++) { CHECKWITHIN(i); }
}
void avoidset::avoidnear(void *owner, float above, const vec &pos, float limit)
{
if(ai::waypoints.empty()) return;
if(clearedwpcaches) buildwpcache();
float limit2 = limit*limit;
#define CHECKNEAR(index) do { \
int n = (index); \
if(n < ai::waypoints.length()) \
{ \
const waypoint &w = ai::waypoints[n]; \
if(w.o.squaredist(pos) < limit2) add(owner, above, n); \
} \
} while(0)
wpcache::node *curnode;
loop(which, NUMWPCACHES) if(wpcaches[which].firstwp >= 0) for(curnode = &wpcaches[which].nodes[0], wpcachestack.setsize(0);;)
{
int axis = curnode->axis();
float dist1 = pos[axis] - curnode->split[0], dist2 = curnode->split[1] - pos[axis];
if(dist1 >= limit)
{
if(dist2 < limit)
{
if(!curnode->isleaf(1)) { curnode += curnode->childindex(1); continue; }
CHECKNEAR(curnode->childindex(1));
}
}
else if(curnode->isleaf(0))
{
CHECKNEAR(curnode->childindex(0));
if(dist2 < limit)
{
if(!curnode->isleaf(1)) { curnode += curnode->childindex(1); continue; }
CHECKNEAR(curnode->childindex(1));
}
}
else
{
if(dist2 < limit)
{
if(!curnode->isleaf(1)) wpcachestack.add(curnode + curnode->childindex(1));
else CHECKNEAR(curnode->childindex(1));
}
curnode += curnode->childindex(0);
continue;
}
if(wpcachestack.empty()) break;
curnode = wpcachestack.pop();
}
for(int i = lastwpcache; i < waypoints.length(); i++) { CHECKNEAR(i); }
}
int avoidset::remap(gameent *d, int n, vec &pos, bool retry)
{
if(!obstacles.empty())
{
int cur = 0;
loopv(obstacles)
{
obstacle &ob = obstacles[i];
int next = cur + ob.numwaypoints;
if(ob.owner != d)
{
for(; cur < next; cur++) if(waypoints[cur] == n)
{
if(ob.above < 0) return retry ? n : -1;
vec above(pos.x, pos.y, ob.above);
if(above.z-d->o.z >= ai::JUMPMAX)
return retry ? n : -1; // too much scotty
int node = closestwaypoint(above, ai::SIGHTMIN, true, d);
if(ai::iswaypoint(node) && node != n)
{ // try to reroute above their head?
if(!find(node, d))
{
pos = ai::waypoints[node].o;
return node;
}
else return retry ? n : -1;
}
else
{
vec old = d->o;
d->o = vec(above).addz(d->eyeheight);
bool col = collide(d, vec(0, 0, 1));
d->o = old;
if(!col)
{
pos = above;
return n;
}
else return retry ? n : -1;
}
}
}
cur = next;
}
}
return n;
}
static inline float heapscore(waypoint *q) { return q->score(); }
bool route(gameent *d, int node, int goal, vector<int> &route, const avoidset &obstacles, int retries)
{
if(waypoints.empty() || !iswaypoint(node) || !iswaypoint(goal) || goal == node || !waypoints[node].links[0])
return false;
static ushort routeid = 1;
static vector<waypoint *> queue;
if(!routeid)
{
loopv(waypoints) waypoints[i].route = 0;
routeid = 1;
}
if(d)
{
if(retries <= 1 && d->ai) loopi(ai::NUMPREVNODES) if(d->ai->prevnodes[i] != node && iswaypoint(d->ai->prevnodes[i]))
{
waypoints[d->ai->prevnodes[i]].route = routeid;
waypoints[d->ai->prevnodes[i]].curscore = -1;
waypoints[d->ai->prevnodes[i]].estscore = 0;
}
if(retries <= 0)
{
loopavoid(obstacles, d,
{
if(iswaypoint(wp) && wp != node && wp != goal && waypoints[node].find(wp) < 0 && waypoints[goal].find(wp) < 0)
{
waypoints[wp].route = routeid;
waypoints[wp].curscore = -1;
waypoints[wp].estscore = 0;
}
});
}
}
waypoints[node].route = routeid;
waypoints[node].curscore = waypoints[node].estscore = 0;
waypoints[node].prev = 0;
queue.setsize(0);
queue.add(&waypoints[node]);
route.setsize(0);
int lowest = -1;
while(!queue.empty())
{
waypoint &m = *queue.removeheap();
float prevscore = m.curscore;
m.curscore = -1;
loopi(MAXWAYPOINTLINKS)
{
int link = m.links[i];
if(!link) break;
if(iswaypoint(link) && (link == node || link == goal || waypoints[link].links[0]))
{
waypoint &n = waypoints[link];
int weight = max(n.weight, 1);
float curscore = prevscore + n.o.dist(m.o)*weight;
if(n.route == routeid && curscore >= n.curscore) continue;
n.curscore = curscore;
n.prev = ushort(&m - &waypoints[0]);
if(n.route != routeid)
{
n.estscore = n.o.dist(waypoints[goal].o)*weight;
if(n.estscore <= WAYPOINTRADIUS*4 && (lowest < 0 || n.estscore <= waypoints[lowest].estscore))
lowest = link;
n.route = routeid;
if(link == goal) goto foundgoal;
queue.addheap(&n);
}
else loopvj(queue) if(queue[j] == &n) { queue.upheap(j); break; }
}
}
}
foundgoal:
routeid++;
if(lowest >= 0) // otherwise nothing got there
{
for(waypoint *m = &waypoints[lowest]; m > &waypoints[0]; m = &waypoints[m->prev])
route.add(m - &waypoints[0]); // just keep it stored backward
}
return !route.empty();
}
VARF(dropwaypoints, 0, 0, 1, { player1->lastnode = -1; });
int addwaypoint(const vec &o, int weight = -1)
{
if(waypoints.length() > MAXWAYPOINTS) return -1;
int n = waypoints.length();
waypoints.add(waypoint(o, weight >= 0 ? weight : getweight(o)));
invalidatewpcache(n);
return n;
}
void linkwaypoint(waypoint &a, int n)
{
loopi(MAXWAYPOINTLINKS)
{
if(a.links[i] == n) return;
if(!a.links[i]) { a.links[i] = n; return; }
}
a.links[rnd(MAXWAYPOINTLINKS)] = n;
}
string loadedwaypoints = "";
static inline bool shouldnavigate()
{
if(dropwaypoints) return true;
loopvrev(players) if(players[i]->aitype != AI_NONE) return true;
return false;
}
static inline bool shoulddrop(gameent *d)
{
return !d->ai && (dropwaypoints || !loadedwaypoints[0]);
}
void inferwaypoints(gameent *d, const vec &o, const vec &v, float mindist)
{
if(!shouldnavigate()) return;
if(shoulddrop(d))
{
if(waypoints.empty()) seedwaypoints();
int from = closestwaypoint(o, mindist, false), to = closestwaypoint(v, mindist, false);
if(!iswaypoint(from)) from = addwaypoint(o);
if(!iswaypoint(to)) to = addwaypoint(v);
if(d->lastnode != from && iswaypoint(d->lastnode) && iswaypoint(from))
linkwaypoint(waypoints[d->lastnode], from);
if(iswaypoint(to))
{
if(from != to && iswaypoint(from) && iswaypoint(to))
linkwaypoint(waypoints[from], to);
d->lastnode = to;
}
}
else d->lastnode = closestwaypoint(v, WAYPOINTRADIUS*2, false, d);
}
void navigate(gameent *d)
{
vec v(d->feetpos());
if(d->state != CS_ALIVE) { d->lastnode = -1; return; }
bool dropping = shoulddrop(d);
int mat = lookupmaterial(v);
if((mat&MATF_CLIP) == MAT_CLIP || (mat&MATF_VOLUME) == MAT_LAVA || mat&MAT_DEATH) dropping = false;
float dist = dropping ? WAYPOINTRADIUS : (d->ai ? WAYPOINTRADIUS : SIGHTMIN);
int curnode = closestwaypoint(v, dist, false, d), prevnode = d->lastnode;
if(!iswaypoint(curnode) && dropping)
{
if(waypoints.empty()) seedwaypoints();
curnode = addwaypoint(v);
}
if(iswaypoint(curnode))
{
if(dropping && d->lastnode != curnode && iswaypoint(d->lastnode))
{
linkwaypoint(waypoints[d->lastnode], curnode);
if(!d->timeinair) linkwaypoint(waypoints[curnode], d->lastnode);
}
d->lastnode = curnode;
if(d->ai && iswaypoint(prevnode) && d->lastnode != prevnode) d->ai->addprevnode(prevnode);
}
else if(!iswaypoint(d->lastnode) || waypoints[d->lastnode].o.squaredist(v) > SIGHTMIN*SIGHTMIN)
d->lastnode = closestwaypoint(v, SIGHTMAX, false, d);
}
void navigate()
{
if(shouldnavigate()) loopv(players) ai::navigate(players[i]);
if(invalidatedwpcaches) clearwpcache(false);
}
void clearwaypoints(bool full)
{
waypoints.setsize(0);
clearwpcache();
if(full)
{
loadedwaypoints[0] = '\0';
dropwaypoints = 0;
}
}
ICOMMAND(clearwaypoints, "", (), clearwaypoints());
void seedwaypoints()
{
if(waypoints.empty()) addwaypoint(vec(0, 0, 0));
loopv(entities::ents)
{
extentity &e = *entities::ents[i];
switch(e.type)
{
case PLAYERSTART: case TELEPORT: case JUMPPAD: case FLAG:
addwaypoint(e.o);
break;
default:
if(validitem(e.type)) addwaypoint(e.o);
break;
}
}
}
void remapwaypoints()
{
vector<ushort> remap;
int total = 0;
loopv(waypoints) remap.add(waypoints[i].links[1] == 0xFFFF ? 0 : total++);
total = 0;
loopvj(waypoints)
{
if(waypoints[j].links[1] == 0xFFFF) continue;
waypoint &w = waypoints[total];
if(j != total) w = waypoints[j];
int k = 0;
loopi(MAXWAYPOINTLINKS)
{
int link = w.links[i];
if(!link) break;
if((w.links[k] = remap[link])) k++;
}
if(k < MAXWAYPOINTLINKS) w.links[k] = 0;
total++;
}
waypoints.setsize(total);
}
bool cleanwaypoints()
{
int cleared = 0;
for(int i = 1; i < waypoints.length(); i++)
{
waypoint &w = waypoints[i];
if(clipped(w.o))
{
w.links[0] = 0;
w.links[1] = 0xFFFF;
cleared++;
}
}
if(cleared)
{
player1->lastnode = -1;
loopv(players) if(players[i]) players[i]->lastnode = -1;
remapwaypoints();
clearwpcache();
return true;
}
return false;
}
bool getwaypointfile(const char *mname, char *wptname)
{
if(!mname || !*mname) mname = getclientmap();
if(!*mname) return false;
nformatstring(wptname, MAXSTRLEN, "media/map/%s.wpt", mname);
path(wptname);
return true;
}
void loadwaypoints(bool force, const char *mname)
{
string wptname;
if(!getwaypointfile(mname, wptname)) return;
if(!force && (waypoints.length() || !strcmp(loadedwaypoints, wptname))) return;
stream *f = opengzfile(wptname, "rb");
if(!f) return;
char magic[4];
if(f->read(magic, 4) < 4 || memcmp(magic, "OWPT", 4)) { delete f; return; }
copystring(loadedwaypoints, wptname);
waypoints.setsize(0);
waypoints.add(vec(0, 0, 0));
ushort numwp = f->getlil<ushort>();
loopi(numwp)
{
if(f->end()) break;
vec o;
o.x = f->getlil<float>();
o.y = f->getlil<float>();
o.z = f->getlil<float>();
waypoint &w = waypoints.add(waypoint(o, getweight(o)));
int numlinks = f->getchar(), k = 0;
loopi(numlinks)
{
if((w.links[k] = f->getlil<ushort>()))
{
if(++k >= MAXWAYPOINTLINKS) break;
}
}
}
delete f;
conoutf("loaded %d waypoints from %s", numwp, wptname);
if(!cleanwaypoints()) clearwpcache();
}
ICOMMAND(loadwaypoints, "s", (char *mname), loadwaypoints(true, mname));
void savewaypoints(bool force, const char *mname)
{
if((!dropwaypoints && !force) || waypoints.empty()) return;
string wptname;
if(!getwaypointfile(mname, wptname)) return;
stream *f = opengzfile(wptname, "wb");
if(!f) return;
f->write("OWPT", 4);
f->putlil<ushort>(waypoints.length()-1);
for(int i = 1; i < waypoints.length(); i++)
{
waypoint &w = waypoints[i];
f->putlil<float>(w.o.x);
f->putlil<float>(w.o.y);
f->putlil<float>(w.o.z);
int numlinks = 0;
loopj(MAXWAYPOINTLINKS) { if(!w.links[j]) break; numlinks++; }
f->putchar(numlinks);
loopj(numlinks) f->putlil<ushort>(w.links[j]);
}
delete f;
conoutf("saved %d waypoints to %s", waypoints.length()-1, wptname);
}
ICOMMAND(savewaypoints, "s", (char *mname), savewaypoints(true, mname));
void delselwaypoints()
{
if(noedit(true)) return;
vec o = vec(sel.o).sub(0.1f), s = vec(sel.s).mul(sel.grid).add(o).add(0.1f);
int cleared = 0;
for(int i = 1; i < waypoints.length(); i++)
{
waypoint &w = waypoints[i];
if(w.o.x >= o.x && w.o.x <= s.x && w.o.y >= o.y && w.o.y <= s.y && w.o.z >= o.z && w.o.z <= s.z)
{
w.links[0] = 0;
w.links[1] = 0xFFFF;
cleared++;
}
}
if(cleared)
{
player1->lastnode = -1;
remapwaypoints();
clearwpcache();
}
}
COMMAND(delselwaypoints, "");
void movewaypoints(const vec &d)
{
if(noedit(true)) return;
int worldsize = getworldsize();
if(d.x < -worldsize || d.x > worldsize || d.y < -worldsize || d.y > worldsize || d.z < -worldsize || d.z > worldsize)
{
clearwaypoints();
return;
}
int cleared = 0;
for(int i = 1; i < waypoints.length(); i++)
{
waypoint &w = waypoints[i];
w.o.add(d);
if(!insideworld(w.o)) { w.links[0] = 0; w.links[1] = 0xFFFF; cleared++; }
}
if(cleared)
{
player1->lastnode = -1;
remapwaypoints();
}
clearwpcache();
}
ICOMMAND(movewaypoints, "iii", (int *dx, int *dy, int *dz), movewaypoints(vec(*dx, *dy, *dz)));
}

View File

@ -684,7 +684,7 @@ namespace game
shoteffects(atk, from, to, d, true, 0, prevaction);
if(d==player1 || d->ai)
if(d==player1)
{
addmsg(N_SHOOT, "rci2i6iv", d, lastmillis-maptime, atk,
(int)(from.x*DMF), (int)(from.y*DMF), (int)(from.z*DMF),
@ -693,7 +693,7 @@ namespace game
}
d->gunwait = attacks[atk].attackdelay;
if(attacks[atk].action != ACT_MELEE && d->ai) d->gunwait += int(d->gunwait*(((101-d->skill)+rnd(111-d->skill))/100.f));
if(attacks[atk].action != ACT_MELEE) d->gunwait += int(d->gunwait*((101+rnd(111))/100.f));
d->totalshots += attacks[atk].damage*attacks[atk].rays;
}
@ -771,14 +771,5 @@ namespace game
if(player1->clientnum>=0 && player1->state==CS_ALIVE) shoot(player1, worldpos); // only shoot when connected to server
updatebouncers(curtime); // need to do this after the player shoots so bouncers don't end up inside player's BB next frame
}
void avoidweapons(ai::avoidset &obstacles, float radius)
{
loopv(projs)
{
projectile &p = projs[i];
obstacles.avoidnear(NULL, p.o.z + attacks[p.atk].exprad + 1, p.o, radius + attacks[p.atk].exprad);
}
}
};

View File

@ -104,7 +104,6 @@ namespace server
extern void localdisconnect(int n);
extern void localconnect(int n);
extern bool allowbroadcast(int n);
extern void recordpacket(int chan, void *data, int len);
extern void parsepacket(int sender, int chan, packetbuf &p);
extern void sendservmsg(const char *s);
extern bool sendpackets(bool force = false);