diff --git a/.gitignore b/.gitignore index 349c36e6..aca7538a 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ config/servers.cfg config/configtemplates.zip config/mapmodelattributes.cfg config/entropy.dat +config/servervita* packages/maps/*.cfg !packages/maps/new_content.cfg packages/maps/*.cgz @@ -37,4 +38,5 @@ clientlog*.txt dumpmapconfig.txt packetlog.txt profile/* +logs/* diff --git a/config/geoip.cfg b/config/geoip.cfg new file mode 100644 index 00000000..50d2b27f --- /dev/null +++ b/config/geoip.cfg @@ -0,0 +1,19 @@ +// geoip table +// =========== +// WARNING: You can NOT use CubeScript in here!! + +// The following schemes are accepted... + +// 1.2.3.4 CC +// 1.2.3.4/24 CC +// 1.2.3.4 - 2.3.4.5 CC +// 38273024 CC +// 38273024/24 CC +// 38273024-38797311 CC +// (with CC being ISO 3166-1 alpha-2 country codes) +// +// check source/tools/ for scripts to convert geoip lists from various free sources +// + + +192.168.0.0/16 XH // home diff --git a/config/maprot.cfg b/config/maprot.cfg index 0476c2ce..d27ce1a4 100644 --- a/config/maprot.cfg +++ b/config/maprot.cfg @@ -1,168 +1,49 @@ // Server Side Maprotations... // WARNING: You can NOT use CubeScript in here!! -// Use the following scheme... -// map:mode:time:allowVote[:minplayer[:maxplayer[:skiplines]]] -// map will be skipped, if minplayer/maxplayer requirements are failed +// This file specifies a set of rules for the maps to be played on the server. The server automatically uses all available +// maps, so this list only needs to specify general settings and exceptions. It is not necessary, to mention every map here. +// The file is read from top to bottom, so if settings conflict, values from further down overwrite earlier ones (like all +// settings in this file overwrite builtin general defaults). Essentially, every combination of map and mode has a set of +// values for all keywords. -// map: map filename without extension -// do only use standard maps and maps from your packages/maps/servermaps folder -// for the flag modes (5, 14 & 15), be sure, the map contains flag bases +// per line: map_name list_of_modes keyword:value [keyword:value] [keyword:value] ... -// mode: Team Deathmatch (0), Deathmatch (2), Pistol Frenzy (6), Team Pistol Frenzy (16), -// Capture the Flag (5), Hunt the Flag (13), Keep the Flag (15), Team Keep the Flag (14), -// Team Survivor (4), Survivor (3), Last Swiss Standing (9), Team Last Swiss Standing (17), -// One Shot One Kill (10), Team One Shot One Kill (11) +// map_name: +// - a corresponding map file needs to be in packages/maps/official, packages/maps/servermaps or packages/maps/servermaps/incoming +// - the file ending "cgz" will be added automatically +// - map name can end with an asterisk to match multiple maps, as single "*" will match all maps +// - map names can only contain: lowercase chars, digits, '_', '-' and '.' -// time: game time in minutes, usually 15 minutes for flag modes, 10 minutes for all other modes +// list_of_modes: (no spaces) +// - can contain any number and combination of these: ctf|dm|htf|ktf|lss|osok|pf|surv|tdm|tktf|tlss|tosok|tpf|tsurv -// vote: (1) allow players to vote for other maps -// (0) only admin can change the map +// keywords: (no spaces between keyword and value allowed) +// - weight: values range from -100 to +100 and influence how often a map+mode will be played (higher number: more often) +// - repeat: values range from -100 to +100 and influence how fast a map+mode can be played again (higher number: sooner) +// - time: default game time in minutes +// - mintime: minimum voteable game time in minutes +// - maxtime: maximum voteable game time in minutes +// - minplayers: minimum number of players for the map+mode to be suggested +// - maxplayers: maximum number of players for the map+mode to be suggested +// - maxteamsize: maximum number of active players per team (total for ffa modes) +// - teamthreshold: number of players that mark the threshold between "better play team" and "better play ffa" +// - manual: 1:map+mode will not be suggested by the server (users need to vote for this map+mode themselves) +// - restrict: 0:player, 1:master, 2:admin, 3:owner 9:elvis (default is 0) +// (weight and repeat values can accumulate within their boundaries) -// minplayer: if set, the server will skip the map, if fewer than required players are connected +// default settings for all maps: +* ctf|dm|htf|ktf|lss|osok|pf|surv|tdm|tktf|tlss|tosok|tpf|tsurv mintime:4 maxtime:20 teamthreshold:4 // all modes +* ctf|htf|tdm|tktf|tlss|tosok|tpf|tsurv time:15 maxteamsize:6 minplayers:4 // team modes +* dm|ktf|lss|osok|pf|surv time:10 maxteamsize:10 maxplayers:7 // ffa modes -// maxplayer: if set, the server will skip the map, if more than allowed players are connected +// default tweaks +* lss|osok|surv|tlss|tosok|tsurv weight:-10 // be careful with suggesting arena modes automatically +* pf|surv|tlss|tpf|tsurv manual:1 // players need to really want these +* ctf weight:10 // some players like this mode -// skiplines: lines to skip in maprot.cfg, after this map got actually played -// this can be used to select one of several lines, instead of playing all lines - -// minplayer and maxplayer numbers are only checked when a new game is picked from the map rotation. -// If the player number changes during gameplay, no action will be taken. - -ac_scaffold : 2: 8: 1: 0: 3: 1 // dm, 8 minutes, 0..3 players -ac_scaffold : 0:10: 1: 4:11: 0 // tdm, 10 minutes, 4..11 players - -ac_industrial :15: 6: 1: 2: 3: 2 // ktf, 6 minutes, 2..3 players -ac_industrial : 5:12: 1: 4:11: 1 // ctf, 12 minutes, 4..11 players -ac_industrial :13:10: 1:12:13: 0 // htf, 10 minutes, 12..13 players - -ac_power : 5:12: 1: 4:12: 1 // ctf, 12 minutes, 4..12 players -ac_power :13:12: 1:13: 0: 0 // htf, 12 minutes, 13.. players - -ac_desert : 2: 8: 1: 0: 4: 1 // dm, 8 minutes, 0..4 players -ac_desert :11:10: 1: 5:11: 0 // tosok, 10 minutes, 5..11 players - -ac_keller : 5:12: 1: 4:12: 1 // ctf, 12 minutes, 4..12 players -ac_keller :13:12: 1:13: 0: 0 // htf, 12 minutes, 13.. players - -ac_arctic : 0:10: 1: 4: 7: 1 // tdm, 10 minutes, 4..7 players -ac_arctic :11:10: 1: 8: 0: 0 // tosok, 10 minutes, 8.. players - -ac_shine : 5:12: 1: 4:13: 1 // ctf, 12 minutes, 4..13 players -ac_shine :13:12: 1:14: 0: 0 // htf, 12 minutes, 14.. players - -ac_elevation :15: 8: 1: 2: 5: 2 // ktf, 8 minutes, 2..5 players -ac_elevation : 5:12: 1: 6:10: 1 // ctf, 12 minutes, 6..10 players -ac_elevation :14:10: 1:11:14: 0 // tktf, 10 minutes, 11..14 players - -ac_desert : 9: 8: 1: 0: 5: 0 // lss, 8 minutes, 0..5 players - -ac_toxic : 2: 8: 1: 2: 5: 1 // dm, 8 minutes, 2..5 players -ac_toxic : 0:10: 1: 6: 0: 0 // tdm, 10 minutes, 6.. players - -ac_mines :15: 6: 1: 2: 3: 2 // ktf, 6 minutes, 2..3 players -ac_mines : 5:12: 1: 4:11: 1 // ctf, 12 minutes, 4..11 players -ac_mines :13:10: 1:12:13: 0 // htf, 10 minutes, 12..13 players - -ac_scaffold :11: 8: 1: 6: 9: 0 // tosok, 8 minutes, 6..9 players - -ac_urban : 0:10: 1: 5: 0: 0 // tdm, 10 minutes, 5.. players - -ac_desert3 : 5:12: 1: 4:10: 1 // ctf, 12 minutes, 4..10 players -ac_desert3 :14:10: 1:11: 0: 0 // tktf, 10 minutes, 11.. players - -ac_depot : 5:12: 1: 4:12: 0 // ctf, 12 minutes, 4..12 players - -ac_desert2 : 0:10: 1: 3: 7: 2 // tdm, 10 minutes, 3..7 players -ac_desert2 :11: 8: 1: 8:10: 1 // tosok, 8 minutes, 8..10 players -ac_desert2 :13:10: 1:11: 0: 0 // htf, 10 minutes, 11.. players - -// one short game on ac_snow, either osok or deathmatch (pseudorandom) -ac_snow :10: 6: 1: 0: 1: 7 // osok, 6 minutes, 0..1 players -ac_snow : 2: 6: 1: 2: 2: 6 // dm, 6 minutes, 2 players -ac_snow :10: 6: 1: 3: 3: 5 // osok, 6 minutes, 3 players -ac_snow : 2: 6: 1: 4: 4: 4 // dm, 6 minutes, 4 players -ac_snow :10: 6: 1: 5: 5: 3 // osok, 6 minutes, 5 players -ac_snow : 2: 6: 1: 6: 6: 2 // dm, 6 minutes, 6 players -ac_snow :10: 6: 1: 7: 7: 1 // osok, 6 minutes, 7 players -ac_snow : 0: 6: 1: 8:11: 0 // tdm, 6 minutes, 8..11 players - -ac_sunset : 5:12: 1: 4:12: 0 // ctf, 12 minutes, 4..12 players - -ac_complex : 0:10: 1: 3:13: 0 // tdm, 10 minutes, 3..13 players - -ac_aqueous :15: 6: 1: 2: 3: 2 // ktf, 6 minutes, 2..3 players -ac_aqueous : 5:12: 1: 4:11: 1 // ctf, 12 minutes, 4..11 players -ac_aqueous :13:10: 1:12:13: 0 // htf, 10 minutes, 12..13 players - -ac_arid : 2: 8: 1: 2: 5: 1 // dm, 8 minutes, 2..5 players -ac_arid :17:10: 1: 6: 0: 0 // tlss, 10 minutes, 6.. players - -ac_edifice :15: 6: 1: 2: 3: 2 // ktf, 6 minutes, 2..3 players -ac_edifice : 5:12: 1: 4:11: 1 // ctf, 12 minutes, 4..11 players -ac_edifice :13:10: 1:12:13: 0 // htf, 10 minutes, 12..13 players - -ac_iceroad : 5:10: 1: 5: 0: 0 // ctf, 10 minutes, 5.. players - -ac_douze :10: 6: 1: 5: 5: 1 // osok, 6 minutes, 5 players -ac_douze : 2: 6: 1: 6: 6: 0 // dm, 6 minutes, 6 players - -ac_gothic :15: 6: 1: 2: 3: 2 // ktf, 6 minutes, 2..3 players -ac_gothic : 5:12: 1: 4:11: 1 // ctf, 12 minutes, 4..11 players -ac_gothic :13:10: 1:12:13: 0 // htf, 10 minutes, 12..13 players - -ac_rattrap : 2: 8: 1: 2: 5: 1 // dm, 8 minutes, 2..5 players -ac_rattrap :16:10: 1: 6: 0: 0 // tpf, 10 minutes, 6.. players - -ac_arabian :15: 6: 1: 2: 3: 2 // ktf, 6 minutes, 2..3 players -ac_arabian : 5:12: 1: 4:11: 1 // ctf, 12 minutes, 4..11 players -ac_arabian :13:10: 1:12:13: 0 // htf, 10 minutes, 12..13 players - -ac_werk :15: 6: 1: 2: 3: 2 // ktf, 6 minutes, 2..3 players -ac_werk : 5:12: 1: 4:11: 1 // ctf, 12 minutes, 4..11 players -ac_werk :13:10: 1:12:13: 0 // htf, 10 minutes, 12..13 players - -ac_stellar :15: 6: 1: 2: 3: 2 // ktf, 6 minutes, 2..3 players -ac_stellar : 5:12: 1: 4:11: 1 // ctf, 12 minutes, 4..11 players -ac_stellar :13:10: 1:12:13: 0 // htf, 10 minutes, 12..13 players - -ac_ingress :15: 6: 1: 2: 3: 2 // ktf, 6 minutes, 2..3 players -ac_ingress : 5:12: 1: 4:11: 1 // ctf, 12 minutes, 4..11 players -ac_ingress :13:10: 1:12:13: 0 // htf, 10 minutes, 12..13 players - -ac_avenue :15: 6: 1: 2: 3: 2 // ktf, 6 minutes, 2..3 players -ac_avenue : 5:12: 1: 4:11: 1 // ctf, 12 minutes, 4..11 players -ac_avenue :13:10: 1:12:13: 0 // htf, 10 minutes, 12..13 players - -ac_edifice :15: 6: 1: 2: 3: 2 // ktf, 6 minutes, 2..3 players -ac_edifice : 5:12: 1: 4:11: 1 // ctf, 12 minutes, 4..11 players -ac_edifice :13:10: 1:12:13: 0 // htf, 10 minutes, 12..13 players - -ac_wasteland : 9: 8: 1: 3: 6: 0 // lss, 8 minutes, 3..6 players - -ac_swamp : 6: 6: 1: 2: 3: 1 // pf, 6 minutes, 2..3 players -ac_swamp :13:12: 1: 4:10: 0 // htf, 12 minutes, 4..10 players - -ac_venison : 2: 8: 1: 0: 4: 2 // dm, 8 minutes, 0..4 players -ac_venison :13:15: 1: 5:10: 1 // htf, 15 minutes, 5..10 players -ac_venison : 5:15: 1:11:14: 0 // ctf, 15 minutes, 11..14 players - -ac_terros : 2: 8: 1: 0: 4: 2 // dm, 8 minutes, 0..4 players -ac_terros :13:15: 1: 5:10: 1 // htf, 15 minutes, 5..10 players -ac_terros : 5:15: 1:11:14: 0 // ctf, 15 minutes, 11..14 players - -ac_lainio : 2: 8: 1: 0: 4: 2 // dm, 8 minutes, 0..4 players -ac_lainio :13:15: 1: 5:10: 1 // htf, 15 minutes, 5..10 players -ac_lainio : 5:15: 1:11:14: 0 // ctf, 15 minutes, 11..14 players - -ac_cavern :15: 6: 1: 2: 3: 2 // ktf, 6 minutes, 2..3 players -ac_cavern :13:15: 1: 5:10: 1 // htf, 15 minutes, 5..10 players -ac_cavern : 5:15: 1:11:14: 0 // ctf, 15 minutes, 11..14 players - -ac_wasteland : 2: 8: 1: 0: 4: 1 // dm, 8 minutes, 0..4 players -ac_wasteland :11:10: 1: 5:11: 0 // tosok, 10 minutes, 5..11 players - - -// Change this file to make the map rotation different and add more.. -// do only use standard maps and maps from your packages/maps/servermaps folder! +// map-specific setting for official maps: (some examples) +//ac_douze ctf manual:1 restrict:9 // disable ctf douze, if it were a possible game mode +//ac_iceroad ctf|dm|htf|ktf|lss|osok|pf|surv|tdm|tktf|tlss|tosok|tpf|tsurv manual:1 restrict:9 // completely disable ac_iceroad +//ac_shine ctf repeat:-50 // not too often diff --git a/config/menus_settings.cfg b/config/menus_settings.cfg index 0451ad78..78b8b981 100644 --- a/config/menus_settings.cfg +++ b/config/menus_settings.cfg @@ -143,7 +143,6 @@ menuitemslider [ Votes: ] 0 2 "$hidevote" ["Always show" "Show until you vot menuitemradio [ Graphics statistics: ] 0 2 "$showstats" [None "FPS Only" All] [ showstats $arg1 ] 1 menuitemslider [ Game time: ] 0 2 "$gametimedisplay" [None "Count backward" "Count forward"] [ gametimedisplay $arg1 ] 1 menuitemslider [ Wall clock: ] 0 6 [findlist $wallclockformatlist $wallclockformat] [None "HH:MM (local, 24h)" "HH:MM:SS (local, 24h)" "HH:MM (local, 12h)" "HH:MM:SS (local, 12h)" "HH:MM (UTC/GMT)" "HH:MM:SS (UTC/GMT)"] [ alias wallclockformat (at $wallclockformatlist $arg1)] 1 -menuitemslider [ Extra messages: ] 0 3 "$hudextras" ["No extra messages" "Show extra messages in console" "Pop up extra messages!" "SPAM EXTRA MESSAGES!"] [ hudextras $arg1 ] menuitemradio [ Hand alignment: ] 0 1 "$righthanded" [Left-handed Right-handed] [ righthanded $arg1 ] 1 menuitemslider [ Max-roll: ] 0 (getvarrange max maxroll) "$maxroll" 1 [ maxroll $arg1 ] menuitemslider [ Spectator max-roll: ] 0 (getvarrange max maxrollremote) "$maxrollremote" 2 [ maxrollremote $arg1 ] @@ -251,9 +250,8 @@ menuitemtextinput "Shotgun: " [ fragmessage SHOTGUN ] [ fragmessage SHOTGUN $arg menuitemtextinput "SMG: " [ fragmessage SUBGUN ] [ fragmessage SUBGUN $arg1 ] [] 15 menuitemtextinput "Sniper: " [ fragmessage SNIPER ] [ fragmessage SNIPER $arg1 ] [] 15 menuitemtextinput "AR: " [ fragmessage ASSAULT ] [ fragmessage ASSAULT $arg1 ] [] 15 -//menuitemtextinput "CPistol: " [ fragmessage CPISTOL ] [ fragmessage CPISTOL $arg1 ] [] 15 menuitemtextinput "Akimbo: " [ fragmessage AKIMBO ] [ fragmessage AKIMBO $arg1 ] [] 15 -menuitem [] -1 +menuitem "" -1 menuitem "\f9Gib messages: " -1 menuitemtextinput "Knife: " [ gibmessage KNIFE ] [ gibmessage KNIFE $arg1 ] [] 15 menuitemtextinput "Shotgun: " [ gibmessage SHOTGUN ] [ gibmessage SHOTGUN $arg1 ] [] 15 diff --git a/config/motd_en.txt b/config/motd_en.txt index f6396309..e6f02396 100644 --- a/config/motd_en.txt +++ b/config/motd_en.txt @@ -2,7 +2,6 @@ // ======================== // WARNING: You can NOT use CubeScript in here!! // -// This text file is read, whenever a the MOTD is sent to a client. // Comments are removed, also leading and trailing blanks and empty lines. // // Don't use more than 80 characters per line. You can use colors and TABs. @@ -14,7 +13,7 @@ // Y: light gray, Z: dark gray // // Example: -// BobsInstagibServer \f3play nicely!\n -// Coloring a motd is fun: \f0Green\f5 and another \fLgreen\f5 from the set.\n +// BobsInstagibServer \f3play nicely! +// Coloring a motd is fun: \f0Green\f5 and another \fLgreen\f5 from the set. // contact me at a@b.c diff --git a/config/scripts.cfg b/config/scripts.cfg index b4497507..edf621ca 100644 --- a/config/scripts.cfg +++ b/config/scripts.cfg @@ -93,6 +93,8 @@ looplist [ listcomplete edittagclip "none plclip clip" listcomplete getvarrange "min max default" listcomplete mapareacheck "vdelta steepest total pprest pp" +listcomplete modeinfo "list clear get all ctf|dm|htf|ktf|lss|osok|pf|surv|tdm|tktf|tlss|tosok|tpf|tsurv" +listcomplete mapartist "print get set clear" listcomplete authsetup "pre priv pub ppass pass passd needpass genpre genpriv genpub newppass newpass unarmed" listcomplete authkey "clear list delete new add selfcert" looplist [ addzipmod zipmodremove zipmodgetdesc ] cmd [complete $cmd "mods" zip] @@ -185,7 +187,6 @@ loop i $NUM_WEAPONS [ (concatword altaction_ $i) = quicknadethrow ] // Quick nad // Use format to define altaction_N aliases from now on, makes for less edits in the future if weapon IDs change. - Bukz // (format altaction_%1 (getgunindex knife)) = [ if (checkmag (currentprimary)) primary [ if (|| (checkmag PISTOL) (checkmag AKIMBO)) secondary [ if (checkmag GRENADE) grenades ] ] ] // Knife // uncomment to switch to primary/secondary/grenades with MOUSE2 depending on ammo content for each. (format altaction_%1 (getgunindex sniper)) = [ setscope 1; onrelease [ setscope 0 ] ] // Sniper -// (format altaction_%1 (getgunindex cpistol)) = [ setburst 1; onrelease [ setburst 0 ] ] // Combat pistol // TODO uncomment when this becomes a priority/testing, for now lets save an unused alias - Bukz // (format altaction_%1 (getgunindex grenades)) = attack // Grenade // with this comment, you throw the nade and the nade is unselected : Brahma oldsens = $sensitivity diff --git a/config/serverblacklist.cfg b/config/serverblacklist.cfg index d4fef5cc..4c5f61c5 100644 --- a/config/serverblacklist.cfg +++ b/config/serverblacklist.cfg @@ -8,6 +8,5 @@ // 1.2.3.4/24 = Ban an IP block (1.2.3.0 - 1.2.3.255) // 1.2.3.4 - 2.3.4.5 = Ban an IP range. -// Hint: sed 's!#!//!g' old_blacklist.txt > new_blacklist.cfg diff --git a/config/servercmdline.txt b/config/servercmdline.txt index 436bb4a4..461fd467 100644 --- a/config/servercmdline.txt +++ b/config/servercmdline.txt @@ -22,6 +22,8 @@ // -f // port, 1..65534, default 28763 // -c // max client number, 1..16, default 6 // -D // number of demos to keep in RAM, default 5 +// -Y // 32-byte private server key (you should maybe include that from another config file and remove it from the server after startup) +// -m // masterserver URL (no parameter uses default URL), if not set, the server will be LAN-only // the switches o, n, n1 and n2 support coloring and character escaping; 'o' also supports escaped newlines // the available colors are: 0: green, 1: blue, 2: yellow, 3: red, 4: gray, 5: white, 6: dark brown, 7: dark red @@ -35,9 +37,13 @@ // -kA // kickthreshold (in seconds) for being AFK, > 30, default 45 // -kB // time in minutes a banned player will stay banned, default 20 // -y // banthreshold, -1..-100, default -6 +// -H // server logfile path, default logs/ // -r // server maprot file, default config/maprot.cfg +// -A // server parameter file, default config/serverparameters.cfg +// -U // vitapath, path & filename prefix to store the server vita database at, default: config/servervita // -X // server passwords file, default config/serverpwd.cfg // -B // server IP blacklist file, default config/serverblacklist.cfg +// -G // server geoip file, default config/geoip.cfg // -K // server nickname blacklist file, default config/nicknameblacklist.cfg // -g // server forbidden words file, default config/forbidden.cfg // -P // voting permission string (read docs/commandline.html) @@ -84,16 +90,9 @@ // 3 WARNING: log only messages of level WARNING and above // 4 ERROR: log only messages of level ERROR // 5 do not write to the log -// -A // Restricts voting for a map/mode to admins. This switch can be used several times. - -// these switches control the naming of demos (see -W) -// --demofilenameformat="%Mmin_%G_%w_%H_%n" // default: "%w_%h_%n_%Mmin_%G" -// --demotimestampformat="%H%M_%Y%m%d" // default: "%Y%m%d_%H%M" -// --demotimelocal=1 // default: 0 // don't use these switches, unless you really know what you're doing: // -u // uprate // -i // ip, only for machines with multiple network interfaces -// -m // masterserver URL (exception: use "-m localhost", if you don't want AC to register at a masterserver at all) diff --git a/config/serverparameters.cfg b/config/serverparameters.cfg new file mode 100644 index 00000000..0fd8a85e --- /dev/null +++ b/config/serverparameters.cfg @@ -0,0 +1,122 @@ +// serverparameters.cfg +// +// parameters from this file are read once per minute by the server +// and are effective immediately + +// each setting consists of a keyword and a value, written like this: keyword:value +// there are no spaces allowed between keyword and value +// using two colons (keyword::value) activates rich-text filtering for the value + + + +// **** Server setup settings **** + +// auth_verify_ip Verify server IP reported by the client during auth +// integer [0..1], default 0, 0:disabled, 1:enabled +auth_verify_ip:0 + +// mandatory_auth Enforce IDs for all clients even on unlisted server/LAN game +// integer [0..1], default 1, 0:disabled, 1:enabled +mandatory_auth:1 + +// vitaautosave Vita file autosave interval in minutes (0: only when server is empty) +// integer [0..1440], default 10 +vitaautosave:10 + +// vitamaxage Omit vitas from autosave, if the last login has been more than the specified number of months ago +// integer [3..120], default 12 +vitamaxage:12 + + +// **** Maprot settings **** + +// gamepenalty_cutoff Number of minutes to remember that a map+mode combination has been played +// integer [30..120], default 60 +gamepenalty_cutoff:60 + +// gamepenalty_random Amount of random weight to add to each map+mode combination +// integer [1..60], default 1 +gamepenalty_random:1 + +// mappenalty_cutoff Number of minutes to remember that a map has been played +// integer [45..240], default 60 +mappenalty_cutoff:60 + +// mappenalty_weight Influence of play history of a map (0: no influence, 100: normal, 200: high influence) +// integer [0..200], default 100 +mappenalty_weight:100 + +// modepenalty_weight Influence of play history of a game mode (-100: stay with played mode, 0: ignore played mode, 100: suggest other modes) +// integer [-100..100], default 50 +modepenalty_weight:50 + +// use_hx_modeinfo List of maprot keywords to copy from map headers +// string [0..159 chars], default "weight|time|mintime|maxtime|minplayers|maxplayers|maxteamsize|teamthreshold" +use_hx_modeinfo:weight|time|mintime|maxtime|minplayers|maxplayers|maxteamsize|teamthreshold + + +// **** Demo recording settings **** + +// demo_debug Excessive logging during demo recording (FIXME) +// integer [0..1], default 1, 0:disabled, 1:enabled +//demo_debug:1 + +// demo_filenameformat Demo file format string +// string [0..63 chars], default "%w_%h_%n_%Mmin_%G" +//demo_filenameformat:%w_%h_%n_%Mmin_%G + +// demo_max_number Maximum number of demo files in RAM +// integer [5..23], default 7 +//demo_max_number:7 + +// demo_path Demo path (and filename) prefix +// string [0..63 chars], default "" +//demo_path: + +// demo_save Write demos to file +// integer [0..1], default 1, 0:disabled, 1:enabled +//demo_save:1 + +// demo_timestampformat Demo timestamp format string +// string [0..23 chars], default "%Y%m%d_%H%M" +//demo_timestampformat:%Y%m%d_%H%M + + +// **** Debug switches **** + +// dumpmaprot dump maprot parameters for all maps (once, to logs/debug/maprot_debug_verbose.txt) +// integer [0..1], default 1, 0:disabled, 1:enabled +dumpmaprot:1 + +// dumpparameters dump server parameters when updated +// integer [0..1], default 0, 0:disabled, 1:enabled +dumpparameters:0 + +// dumpsuggestions dump maprot suggestions whenever recalculated +// integer [0..1], default 1, 0:disabled, 1:enabled +dumpsuggestions:1 + + +// **** Commandline switch overrides **** + +// logthreshold_console Console log level +// integer [0..5], default 2, 0:debug, 1:verbose, 2:info, 3:warning, 4:error, 5:disabled +//logthreshold_console:2 + +// logthreshold_file Logfile log level +// integer [0..5], default 2, 0:debug, 1:verbose, 2:info, 3:warning, 4:error, 5:disabled +//logthreshold_file:2 + +// logthreshold_syslog Syslog log level +// integer [0..5], default 5, 0:debug, 1:verbose, 2:info, 3:warning, 4:error, 5:disabled +//logthreshold_syslog:5 + + +// **** Server load statistics **** + +// stat_mainlog_peaklevel Main log peak fill level (0..100%) + +// stat_theadlog_peaklevel Threadlog peak fill level (0..100%) + + + diff --git a/source/enet/unix.c b/source/enet/unix.c index be621915..cedaec5b 100644 --- a/source/enet/unix.c +++ b/source/enet/unix.c @@ -131,8 +131,10 @@ enet_address_get_host_ip (const ENetAddress * address, char * name, size_t nameL #else char * addr = inet_ntoa (* (struct in_addr *) & address -> host); if (addr != NULL) + { strncpy (name, addr, nameLength); name[nameLength - 1] = '\0'; + } else #endif return -1; diff --git a/source/src/Makefile b/source/src/Makefile index 0f220a93..aae58703 100644 --- a/source/src/Makefile +++ b/source/src/Makefile @@ -131,7 +131,6 @@ SERVER_OBJS= \ tools-standalone.o \ wizard-standalone.o MASTER_OBJS= \ - crypto-standalone.o \ stream-standalone.o \ command-standalone.o \ master-standalone.o diff --git a/source/src/autodownload.cpp b/source/src/autodownload.cpp index adfbd05e..aec31ed6 100644 --- a/source/src/autodownload.cpp +++ b/source/src/autodownload.cpp @@ -76,7 +76,8 @@ int pingpckserver(void *data) // fetch updates.txt from a media server, measure u.set(s->host); s->resolved = h.set_host(u.domain) ? 1 : 0; SDL_mutexP(pckpinglog_lock); - cvecprintf(pckping_log, "lresolving hostname %s %s (%s)\n", u.domain, s->resolved ? "succeeded" : "failed", iptoa(ntohl(h.ip.host))); + string b; + cvecprintf(pckping_log, "lresolving hostname %s %s (%s)\n", u.domain, s->resolved ? "succeeded" : "failed", iptoa(ENET_NET_TO_HOST_32(h.ip.host), b)); SDL_mutexV(pckpinglog_lock); if(s->resolved) { diff --git a/source/src/bot/ac_bot.cpp b/source/src/bot/ac_bot.cpp index 6199d44f..56d161a0 100644 --- a/source/src/bot/ac_bot.cpp +++ b/source/src/bot/ac_bot.cpp @@ -22,7 +22,7 @@ extern void spawnstate(playerent *d); void CACBot::Spawn() { // Init all bot variabeles - m_pMyEnt->nextprimary = 2 + rnd(5); // 2011jan18:ft: 2 == GUN_CARBINE, GUN_SHOTGUN, GUN_SUBGUN, GUN_SNIPER, GUN_ASSAULT - if CPISTOL is activated this needs a FIXME to rnd(6) + m_pMyEnt->nextprimary = 2 + rnd(5); // 2 == GUN_CARBINE, GUN_SHOTGUN, GUN_SUBGUN, GUN_SNIPER, GUN_ASSAULT m_pMyEnt->targetyaw = m_pMyEnt->targetpitch = 0.0f; m_pMyEnt->pBot = this; diff --git a/source/src/bot/ac_bot.h b/source/src/bot/ac_bot.h index 55d5cfe4..a2c8d8c9 100644 --- a/source/src/bot/ac_bot.h +++ b/source/src/bot/ac_bot.h @@ -14,7 +14,7 @@ #define AC_BOT_H //#define BOTDEBUG -#define MAX_WEAPONS 10 +#define MAX_WEAPONS 9 // NUM_GUNS class CACBot: public CBot { diff --git a/source/src/bot/ac_bot_ai.cpp b/source/src/bot/ac_bot_ai.cpp index e9234776..a17be56a 100644 --- a/source/src/bot/ac_bot_ai.cpp +++ b/source/src/bot/ac_bot_ai.cpp @@ -24,7 +24,6 @@ weaponinfo_s WeaponInfoTable[MAX_WEAPONS] = { TYPE_AUTO, 0.0f, 25.0f, 3.0f, 60.0f, 10 }, // SUBGUN { TYPE_SNIPER, 10.0f, 125.0f, 3.0f, 200.0f, 3 }, // SNIPER { TYPE_AUTO, 20.0f, 80.0f, 3.0f, 150.0f, 6 }, // ASSAULT - { TYPE_AUTO, 0.0f, 0.0f, 3.0f, 0.0f, 6 }, // CPISTOL { TYPE_GRENADE, 5.0f, 30.0f, 3.0f, 50.0f, 1 }, // GRENADE { TYPE_AUTO, 0.0f, 50.0f, 3.0f, 50.0f, 0 } // AKIMBO }; diff --git a/source/src/bot/bot.cpp b/source/src/bot/bot.cpp index 1fa0c83d..06ff761b 100644 --- a/source/src/bot/bot.cpp +++ b/source/src/bot/bot.cpp @@ -17,7 +17,6 @@ vector bots; extern int triggertime; extern itemstat itemstats[]; -extern ENetHost *clienthost; const vec g_vecZero(0, 0, 0); diff --git a/source/src/bot/bot_waypoint.cpp b/source/src/bot/bot_waypoint.cpp index 30551b70..8dbeec50 100644 --- a/source/src/bot/bot_waypoint.cpp +++ b/source/src/bot/bot_waypoint.cpp @@ -15,11 +15,8 @@ vec v_debuggoal = g_vecZero; CACWaypointClass WaypointClass; VAR(xhairwpsel, 0, 1, 1); -// FIXME: multiple selections support ? -// well, TBH I haven't groked this myself yet, but the following curselection segfaults the client, so I'm just putting in a sane-variant until someone groks it ;-) - ft 2011sep28 extern vector sels; -//#define curselection (xhairwpsel ? vec(sels.last().x, sels.last().y, S(sels.last().x, sels.last().y)->floor+2.0f) : vec(player1->o.x, player1->o.y, player1->o.z)) -#define curselection (vec(player1->o.x, player1->o.y, player1->o.z)) +#define curselection (xhairwpsel && sels.length() ? vec(sels.last().x, sels.last().y, S(sels.last().x, sels.last().y)->floor+2.0f) : vec(player1->o.x, player1->o.y, player1->o.z)) // Waypoint class begin diff --git a/source/src/client.cpp b/source/src/client.cpp index fffa8290..67f19724 100644 --- a/source/src/client.cpp +++ b/source/src/client.cpp @@ -9,7 +9,6 @@ ENetHost *clienthost = NULL; ENetPeer *curpeer = NULL, *connpeer = NULL; int connmillis = 0, connattempts = 0, discmillis = 0; SVAR(curdemofile, "n/a"); -extern bool clfail, cllock; extern int searchlan; int getclientnum() { return player1 ? player1->clientnum : -1; } @@ -52,20 +51,12 @@ void abortconnect() connectrole = CR_DEFAULT; if(connpeer->state!=ENET_PEER_STATE_DISCONNECTED) enet_peer_reset(connpeer); connpeer = NULL; -#if 0 - if(!curpeer) - { - enet_host_destroy(clienthost); - clienthost = NULL; - } -#endif } void connectserv_(const char *servername, int serverport = 0, const char *password = NULL, int role = CR_DEFAULT) { if(serverport <= 0) serverport = CUBE_DEFAULT_SERVER_PORT; if(watchingdemo) enddemoplayback(); - if(!clfail && cllock && searchlan<2) return; if(connpeer) { @@ -119,6 +110,7 @@ void connectserv(char *servername, int *serverport, char *password) modprotocol = false; connectserv_(servername, *serverport, password); } +COMMANDN(connect, connectserv, "sis"); void connectadmin(char *servername, int *serverport, char *password) { @@ -126,18 +118,21 @@ void connectadmin(char *servername, int *serverport, char *password) if(!password[0]) return; connectserv_(servername, *serverport, password, CR_ADMIN); } +COMMAND(connectadmin, "sis"); void lanconnect() { modprotocol = false; connectserv_(NULL); } +COMMAND(lanconnect, ""); void modconnectserv(char *servername, int *serverport, char *password) { modprotocol = true; connectserv_(servername, *serverport, password); } +COMMANDN(modconnect, modconnectserv, "sis"); void modconnectadmin(char *servername, int *serverport, char *password) { @@ -145,17 +140,14 @@ void modconnectadmin(char *servername, int *serverport, char *password) if(!password[0]) return; connectserv_(servername, *serverport, password, CR_ADMIN); } +COMMAND(modconnectadmin, "sis"); void modlanconnect() { modprotocol = true; connectserv_(NULL); } - -void whereami() -{ - conoutf("you are at (%.2f,%.2f)", player1->o.x, player1->o.y); -} +COMMAND(modlanconnect, ""); void disconnect(int onlyclean, int async) { @@ -164,7 +156,7 @@ void disconnect(int onlyclean, int async) { if(!discmillis) { - enet_peer_disconnect(curpeer, DISC_NONE); + enet_peer_disconnect(curpeer, DISC_BECAUSE); enet_host_flush(clienthost); discmillis = totalmillis; } @@ -192,13 +184,6 @@ void disconnect(int onlyclean, int async) audiomgr.clearworldsounds(false); localdisconnect(); } -#if 0 - if(!connpeer && clienthost) - { - enet_host_destroy(clienthost); - clienthost = NULL; - } -#endif if(!onlyclean) localconnect(); exechook(HOOK_SP_MP, "onDisconnect", "%d", -1); } @@ -219,6 +204,9 @@ void trydisconnect() conoutf("attempting to disconnect..."); disconnect(0, !discmillis); } +COMMANDN(disconnect, trydisconnect, ""); + +// core game function: ingame chat void _toserver(char *text, int msg, int msgt) { @@ -226,25 +214,34 @@ void _toserver(char *text, int msg, int msgt) if(!toteam && text[0] == '%' && strlen(text) > 1) text++; // convert team-text to normal-text if no team-mode is active if(toteam) text++; filtertext(text, text, FTXT__CHAT); - trimtrailingwhitespace(text); if(servstate.mastermode == MM_MATCH && servstate.matchteamsize && !team_isactive(player1->team) && !(player1->team == TEAM_SPECT && player1->clientrole == CR_ADMIN)) toteam = true; // spect chat - if(*text) - { - if(msg == SV_TEXTME) conoutf("\f%d%s %s", toteam ? 1 : 0, colorname(player1), highlight(text)); - else conoutf("%s:\f%d %s", colorname(player1), toteam ? 1 : 0, highlight(text)); - addmsg(toteam ? msgt : msg, "rs", text); - } + if(*text) addmsg(toteam ? msgt : msg, "rs", text); } void toserver(char *text) { _toserver(text, SV_TEXT, SV_TEAMTEXT); } +COMMANDN(say, toserver, "c"); void toserverme(char *text) { _toserver(text, SV_TEXTME, SV_TEAMTEXTME); } +COMMANDN(me, toserverme, "c"); + +void pm(char *text) +{ + char *msg; + int cn = (int) strtol(text, &msg, 10); + if(msg != text && getclient(cn)) + { + filtertext(msg, msg, FTXT__CHAT | FTXT_CROPWHITE_LEAD); + if(*msg) addmsg(SV_TEXTPRIVATE, "ris", cn, msg); + } + else conoutf("\f3pm: invalid client number specified"); +} +COMMAND(pm, "c"); void echo(char *text) { @@ -256,8 +253,10 @@ void echo(char *text) } while(s); } +COMMAND(echo, "c"); VARP(allowhudechos, 0, 1, 1); + void hudecho(char *text) { char *b, *s = strtok_r(text, "\n", &b); @@ -269,65 +268,12 @@ void hudecho(char *text) } while(s); } - -void pm(char *text) -{ - if(!text || !text[0]) return; - int cn = -1; - char digit; - while ((digit = *text++) != '\0') - { - if (digit < '0' || digit > '9') break; - if(cn < 0) cn = 0; - else cn *= 10; - cn += digit - '0'; - } - playerent *to = getclient(cn); - if(!to) - { - conoutf("invalid client number specified"); - return; - } - - if(!isspace(digit)) { --text; } - - // FIXME: - /*if(!text || !text[0] || !isdigit(text[0])) return; - int cn = -1; - char *numend = strpbrk(text, " \t"); - if(!numend) return; - string cnbuf; - copystring(cnbuf, text, min(numend-text+1, MAXSTRLEN)); - cn = atoi(cnbuf); - playerent *to = getclient(cn); - if(!to) - { - conoutf("invalid client number specified"); - return; - } - - if(*numend) numend++;*/ - // :FIXME - - filtertext(text, text, FTXT__CHAT); - trimtrailingwhitespace(text); - - addmsg(SV_TEXTPRIVATE, "ris", cn, text); - conoutf("to %s (PM):\f9 %s", colorname(to), highlight(text)); -} -COMMAND(pm, "c"); - -COMMAND(echo, "c"); COMMAND(hudecho, "c"); -COMMANDN(say, toserver, "c"); -COMMANDN(me, toserverme, "c"); -COMMANDN(connect, connectserv, "sis"); -COMMAND(connectadmin, "sis"); -COMMAND(lanconnect, ""); -COMMANDN(modconnect, modconnectserv, "sis"); -COMMAND(modconnectadmin, "sis"); -COMMAND(modlanconnect, ""); -COMMANDN(disconnect, trydisconnect, ""); + +void whereami() +{ + conoutf("you are at (%.2f,%.2f)", player1->o.x, player1->o.y); +} COMMAND(whereami, ""); void current_version(char *text) @@ -433,67 +379,50 @@ void c2sinfo(playerent *d) // send update to the server if(d->state==CS_ALIVE || d->state==CS_EDITING) { + ASSERT(!(d->crouching && d->onladder)); // onladder is never set while crouching - and we're going to rely on it ;) + ASSERT(!(d->move < -1 || d->move > 1 || d->strafe < -1 || d->strafe > 1)); packetbuf q(100); int cn = d->clientnum, - x = (int)(d->o.x*DMF), // quantize coordinates to 1/16th of a cube, between 1 and 3 bytes - y = (int)(d->o.y*DMF), - z = (int)((d->o.z - d->eyeheight)*DMF), - ya = (int)((512 * d->yaw) / 360.0f), - pi = (int)((127 * d->pitch) / 90.0f), - r = (int)(31*d->roll/20), - dxt = (int)(d->vel.x*DVELF), - dyt = (int)(d->vel.y*DVELF), - dzt = (int)(d->vel.z*DVELF), - dx = dxt - d->vel_t.i[0], - dy = dyt - d->vel_t.i[1], - dz = dzt - d->vel_t.i[2], - // pack rest in 1 int: strafe:2, move:2, onfloor:1, onladder: 1 - f = (d->strafe&3) | ((d->move&3)<<2) | (((int)d->onfloor)<<4) | (((int)d->onladder)<<5) | ((d->lifesequence&1)<<6) | (((int)d->crouching)<<7), - g = (dx?1:0) | ((dy?1:0)<<1) | ((dz?1:0)<<2) | ((r?1:0)<<3) | (((int)d->scoping)<<4) | (((int)d->shoot)<<5); - d->vel_t.i[0] = dxt; - d->vel_t.i[1] = dyt; - d->vel_t.i[2] = dzt; - int usefactor = sfactor < 7 ? 7 : sfactor, sizexy = 1 << (usefactor + 4); + x = (int)(d->o.x*DMF + 0.5f), // quantize coordinates to 1/16th of a cube, between 1 and 3 bytes + y = (int)(d->o.y*DMF + 0.5f), + z = (int)floorf((d->o.z - d->eyeheight)*DMF + 0.5f), + zsign = z < 0 ? 1 : 0, + ya = encodeyaw(d->yaw), + pi = encodepitch(d->pitch), + dx = (int)floorf(d->vel.x*DVELF + 0.5f), + dy = (int)floorf(d->vel.y*DVELF + 0.5f), + dz = (int)floorf(d->vel.z*DVELF + 0.5f); + int f = (d->strafe + 4 + d->move * 3 + (d->onladder ? 9 : 0) + (d->crouching ? 18 : 0)) // pack 6 bit into 5 with ternary logic :) + | (((int)d->scoping)<<5) | ((d->lifesequence&1)<<6) | (((int)d->onfloor)<<7) | (((int)d->jumpd)<<8) | (((int)(dx||dy||dz))<<9) | (zsign << 10); // number of used bits: FLAGBITS + int usefactor = sfactor < 7 ? 7 : sfactor; + if(zsign) z = -z; if(cn >= 0 && cn < 32 && usefactor <= 7 + 3 && // map size 7..10 - x >= 0 && x < sizexy && - y >= 0 && y < sizexy && + !((x | y) & ~((1 << (usefactor + 4)) - 1)) && z >= -2047 && z <= 2047 && - ya >= 0 && ya < 512 && - pi >= -128 && pi <= 127 && - r >= -32 && r <= 31 && - dx >= -8 && dx <= 7 && // FIXME + dx >= -8 && dx <= 7 && dy >= -8 && dy <= 7 && dz >= -8 && dz <= 7) { // compact POS packet - bool noroll = !r, novel = !dx && !dy && !dz; bitbuf b(q); - putint(q, SV_POSC); + putint(q, SV_POSC + usefactor - 7); b.putbits(5, cn); - b.putbits(2, usefactor - 7); b.putbits(usefactor + 4, x); b.putbits(usefactor + 4, y); - b.putbits(9, ya); - b.putbits(8, pi + 128); - b.putbits(1, noroll ? 1 : 0); - if(!noroll) b.putbits(6, r + 32); - b.putbits(1, novel ? 1 : 0); - if(!novel) + b.putbits(YAWBITS, ya); + b.putbits(PITCHBITS, pi); + b.putbits(FLAGBITS, f); + if(f & (1 << 9)) // hasvel { b.putbits(4, dx + 8); b.putbits(4, dy + 8); b.putbits(4, dz + 8); } - b.putbits(8, f); - b.putbits(1, z < 0 ? 1 : 0); - if(z < 0) z = -z; // z is encoded with 3..10 bits minimum (fitted to byte boundaries), or full 11 bits if necessary - int s = (b.rembits() - 1 + 8) % 8; + int s = (b.rembits() - 1 + 8) % 8; // z is encoded with 3..10 bits minimum (fitted to byte boundaries), or full 11 bits if necessary if(s < 3) s += 8; if(z >= (1 << s)) s = 11; b.putbits(1, s == 11 ? 1 : 0); b.putbits(s, z); - b.putbits(1, d->scoping ? 1 : 0); - b.putbits(1, d->shoot ? 1 : 0); } else { // classic POS packet @@ -502,17 +431,16 @@ void c2sinfo(playerent *d) // send update to the server putuint(q, x); putuint(q, y); putuint(q, z); - putuint(q, (int)d->yaw); - putint(q, (int)d->pitch); - putuint(q, g); - if (r) putint(q, (int)(125*d->roll/20)); - if (dx) putint(q, dx); - if (dy) putint(q, dy); - if (dz) putint(q, dz); - putuint(q, f); + putuintn(q, (ya << (uint64_t)(FLAGBITS + PITCHBITS)) | (pi << FLAGBITS) | f, (YAWBITS + PITCHBITS + FLAGBITS + 7) / 8); + if(f & (1<<9)) + { + putint(q, dx); + putint(q, dy); + putint(q, dz); + } } sendpackettoserv(0, q.finalize()); - d->shoot = false; + d->jumpd = false; } if(sendmapidenttoserver || messages.length() || totalmillis-lastping>250) @@ -582,6 +510,10 @@ void sendintro() connectrole = CR_DEFAULT; putint(p, player1->nextprimweap->type); loopi(2) putint(p, player1->skin(i)); + putint(p, player1->maxroll); + putint(p, player1->maxrolleffect); + putint(p, player1->ffov); + putint(p, player1->scopefov); sendpackettoserv(1, p.finalize()); } @@ -629,8 +561,6 @@ void gets2c() // get updates from the server case ENET_EVENT_TYPE_DISCONNECT: { - extern const char *disc_reason(int reason); - if(event.data>=DISC_NUM) event.data = DISC_NONE; if(event.peer==connpeer) { conoutf("\f3could not connect to server"); @@ -649,84 +579,13 @@ void gets2c() // get updates from the server } } -#if 0 -// for AUTH: - -vector authkeys; - -VARP(autoauth, 0, 1, 1); - -authkey *findauthkey(const char *desc) -{ - loopv(authkeys) if(!strcmp(authkeys[i]->desc, desc) && !strcmp(authkeys[i]->name, player1->name)) return authkeys[i]; - loopv(authkeys) if(!strcmp(authkeys[i]->desc, desc)) return authkeys[i]; - return NULL; -} - -void addauthkey(const char *name, const char *key, const char *desc) -{ - loopvrev(authkeys) if(!strcmp(authkeys[i]->desc, desc) && !strcmp(authkeys[i]->name, name)) delete authkeys.remove(i); - if(name[0] && key[0]) authkeys.add(new authkey(name, key, desc)); -} - -bool _hasauthkey(const char *name, const char *desc) -{ - if(!name[0] && !desc[0]) return authkeys.length() > 0; - loopvrev(authkeys) if(!strcmp(authkeys[i]->desc, desc) && !strcmp(authkeys[i]->name, name)) return true; - return false; -} - -void genauthkey(const char *secret) -{ - if(!secret[0]) { conoutf("you must specify a secret password"); return; } - vector privkey, pubkey; - genprivkey(secret, privkey, pubkey); - conoutf("private key: %s", privkey.getbuf()); - conoutf("public key: %s", pubkey.getbuf()); -} - -void saveauthkeys() -{ - if(authkeys.length()) - { - stream *f = openfile("config/auth.cfg", "w"); - if(!f) { conoutf("failed to open config/auth.cfg for writing"); return; } - loopv(authkeys) - { - authkey *a = authkeys[i]; - f->printf("authkey \"%s\" \"%s\" \"%s\"\n", a->name, a->key, a->desc); - } - conoutf("saved authkeys to config/auth.cfg"); - delete f; - } - else conoutf("you need to use 'addauthkey USER KEY DESC' first; one DESC 'public', one DESC empty(=private)"); -} - -bool tryauth(const char *desc) -{ - authkey *a = findauthkey(desc); - if(!a) return false; - a->lastauth = lastmillis; - addmsg(SV_AUTHTRY, "rss", a->desc, a->name); - return true; -} - -COMMANDN(authkey, addauthkey, "sss"); -COMMANDF(hasauthkey, "ss", (char *name, char *desc) { intret(_hasauthkey(name, desc) ? 1 : 0); }); -COMMAND(genauthkey, "s"); -COMMAND(saveauthkeys, ""); -COMMANDF(auth, "s", (char *desc) { intret(tryauth(desc)); }); - -// :for AUTH - -#endif - // sendmap/getmap commands, should be replaced by more intuitive map downloading vector securemaps; -void resetsecuremaps() { securemaps.deletearrays(); } -void securemap(char *map) { if(map) securemaps.add(newstring(map)); } +COMMANDF(resetsecuremaps, "", () { securemaps.deletearrays(); }); +COMMANDF(securemap, "s", (char *map) { if(map) securemaps.add(newstring(map)); }); + bool securemapcheck(const char *map, bool msg) { if(strstr(map, "maps/")==map || strstr(map, "maps\\")==map) map += strlen("maps/"); @@ -735,7 +594,7 @@ bool securemapcheck(const char *map, bool msg) if(msg) { conoutf("\f3Map \f4%s\f3 is secured. This means you CAN'T send, receive or overwrite it.", map); - if(connected) + if(curpeer) { conoutf("\f3If you get this error often, you (or the server) may be running an outdated game."); conoutf("\f3You can check for updates at \f1http://assault.cubers.net/download.html"); @@ -784,11 +643,12 @@ void sendmap(char *mapname) sendpackettoserv(2, p.finalize()); conoutf("sending map %s to server...", mapname); } +COMMAND(sendmap, "s"); void getmap(char *name, char *callback) { if((!name || !*name) - || (connected && !strcmp(name, getclientmap())) ) + || (curpeer && !strcmp(name, getclientmap())) ) { conoutf("requesting map from server..."); packetbuf p(10, ENET_PACKET_FLAG_RELIABLE); @@ -806,6 +666,7 @@ void getmap(char *name, char *callback) else conoutf("\f3map download failed"); } } +COMMAND(getmap, "ss"); void deleteservermap(char *mapname) { @@ -813,6 +674,7 @@ void deleteservermap(char *mapname) if(!*name || securemapcheck(name)) return; addmsg(SV_REMOVEMAP, "rs", name); } +COMMAND(deleteservermap, "s"); string demosubpath; void getdemo(int *idx, char *dsp) @@ -828,6 +690,7 @@ void getdemo(int *idx, char *dsp) else conoutf("getting demo %d...", *idx); addmsg(SV_GETDEMO, "ri", *idx); } +COMMAND(getdemo, "is"); void listdemos() { @@ -839,6 +702,7 @@ void listdemos() conoutf("listing demos..."); addmsg(SV_LISTDEMOS, "r"); } +COMMAND(listdemos, ""); void shiftgametime(int newmillis) { @@ -875,13 +739,6 @@ void rewinddemo(char *seconds) } COMMANDN(rewind, rewinddemo, "s"); -COMMAND(sendmap, "s"); -COMMAND(getmap, "ss"); -COMMAND(deleteservermap, "s"); -COMMAND(resetsecuremaps, ""); -COMMAND(securemap, "s"); -COMMAND(getdemo, "is"); -COMMAND(listdemos, ""); COMMANDF(watchingdemo, "", () { intret(watchingdemo); }); diff --git a/source/src/clientgame.cpp b/source/src/clientgame.cpp index 5882c963..97198667 100644 --- a/source/src/clientgame.cpp +++ b/source/src/clientgame.cpp @@ -5,7 +5,6 @@ int nextmode = 0; // nextmode becomes gamemode after next map load VAR(gamemode, 1, 0, 0); -VAR(nextGameMode, 1, 0, 0); VARP(modeacronyms, 0, 1, 1); flaginfo flaginfos[2]; @@ -15,8 +14,6 @@ mapdim_s clmapdims; // min/max X/Y and delta X/Y and min/max Z void mode(int *n) { nextmode = *n; - nextGameMode = nextmode; - if(m_mp(*n) || !multiplayer()) addmsg(SV_GAMEMODE, "ri", *n); } COMMAND(mode, "i"); @@ -45,15 +42,12 @@ void setskin(playerent *pl, int skin, int team) pl->setskin(team, skin); } -extern char *global_name; - bool duplicatename(playerent *d, char *name = NULL) { if(!name) name = d->name; if(d!=player1 && !strcmp(name, player1->name) && !watchingdemo) return true; if(!strcmp(name, "you")) return true; loopv(players) if(players[i] && d!=players[i] && !strcmp(name, players[i]->name)) return true; - global_name = player1->name; // this certainly is not the best place to put this return false; } @@ -115,6 +109,7 @@ void ignore(int *cn) playerent *d = getclient(*cn); if(d && d != player1) d->ignored = true; } +COMMAND(ignore, "i"); void listignored() { @@ -124,17 +119,20 @@ void listignored() if(*pl) conoutf("ignored players: %s", pl + 2); else conoutf("no players are ignored"); } +COMMAND(listignored, ""); void clearignored(int *cn) { loopv(players) if(players[i] && (*cn < 0 || *cn == i)) players[i]->ignored = false; } +COMMAND(clearignored, "i"); void muteplayer(int *cn) { playerent *d = getclient(*cn); if(d && d != player1) d->muted = true; } +COMMAND(muteplayer, "i"); void listmuted() { @@ -144,17 +142,12 @@ void listmuted() if(*pl) conoutf("muted players: %s", pl + 2); else conoutf("no players are muted"); } +COMMAND(listmuted, ""); void clearmuted(char *cn) { loopv(players) if(players[i] && (*cn < 0 || *cn == i)) players[i]->muted = false; } - -COMMAND(ignore, "i"); -COMMAND(listignored, ""); -COMMAND(clearignored, "i"); -COMMAND(muteplayer, "i"); -COMMAND(listmuted, ""); COMMAND(clearmuted, "i"); void newname(const char *name) @@ -171,6 +164,7 @@ void newname(const char *name) } else conoutf("your name is: %s", player1->name); } +COMMANDN(name, newname, "s"); SVARFF(curname, { curname = exchangestr(curname, player1->name); }, {} ); @@ -191,12 +185,14 @@ void newteam(char *name) } else conoutf("your team is: %s", team_string(player1->team)); } +COMMANDN(team, newteam, "s"); void benchme() { if(team_isactive(player1->team) && servstate.mastermode == MM_MATCH) addmsg(SV_SWITCHTEAM, "ri", team_tospec(player1->team)); } +COMMAND(benchme, ""); int _setskin(int s, int t) { @@ -217,14 +213,11 @@ void curmodeattr(char *attr) else if(!strcmp(attr, "bot")) { intret(m_botmode); return; } intret(0); } +COMMAND(curmodeattr, "s"); -COMMANDN(team, newteam, "s"); -COMMANDN(name, newname, "s"); -COMMAND(benchme, ""); COMMANDF(isclient, "i", (int *cn) { intret(getclient(*cn) != NULL ? 1 : 0); } ); COMMANDF(curmastermode, "", (void) { intret(servstate.mastermode); }); COMMANDF(curautoteam, "", (void) { intret(servstate.autoteam); }); -COMMAND(curmodeattr, "s"); COMMANDF(curmap, "", (void) { result(behindpath(getclientmap())); }); COMMANDF(curplayers, "", (void) { intret(players.length() + 1); }); VARP(showscoresondeath, 0, 1, 1); @@ -295,11 +288,8 @@ void playerinfo(int *cn, const char *attr) ATTR_INT(skin_rvsf, p->skin(TEAM_RVSF)); ATTR_INT(skin, p->skin(player1->team)); - string addrstr = ""; - uint2ip(p->address, addr); - if(addr[3] != 0 || player1->clientrole==CR_ADMIN) - formatstring(addrstr)("%d.%d.%d.%d", addr[0], addr[1], addr[2], addr[3]); // full IP - else formatstring(addrstr)("%d.%d.%d.x", addr[0], addr[1], addr[2]); // censored IP + string addrstr; + iptoa(p->address, addrstr); ATTR_STR(ip, addrstr); conoutf("invalid attribute: %s", attr); @@ -373,7 +363,6 @@ const char *currentserver(int i) // [client version] // using the curpeer directly we can get the info of our currently connected server string r; r[0] = '\0'; - extern ENetPeer *curpeer; if(curpeer) { switch(i) @@ -453,10 +442,10 @@ void deathstate(playerent *pl) { if(showscoresondeath) showscores(true); setscope(false); - setburst(false); if(editmode) toggleedit(true); if(pl->team == TEAM_SPECT) spectatemode(SM_FLY); else if(team_isspect(pl->team)) spectatemode(SM_FOLLOW1ST); + if(pl->spectatemode == SM_DEATHCAM) addmsg(SV_SPECTCN, "ri", (player1->followplayercn = FPCN_DEATHCAM)); } else pl->resetinterp(); } @@ -468,7 +457,6 @@ void spawnstate(playerent *d) // reset player state not persistent if(d==player1) { setscope(false); - setburst(false); } if(d->deaths==0) d->resetstats(); } @@ -657,6 +645,7 @@ void addsleep_(int *msec, char *cmd, int *persist) { addsleep(*msec, cmd, *persist != 0); } +COMMANDN(sleep, addsleep_, "isi"); void resetsleep(bool force) { @@ -669,7 +658,6 @@ void resetsleep(bool force) sleepssemaphore->post(); } -COMMANDN(sleep, addsleep_, "isi"); COMMANDF(resetsleeps, "", (void) { resetsleep(true); }); void updateworld(int curtime, int lastmillis) // main game update loop @@ -800,7 +788,6 @@ void respawnself() } showscores(false); setscope(false); - setburst(false); lasthit = 0; spawnplayer(player1); player1->lifesequence++; @@ -809,15 +796,6 @@ void respawnself() } } -extern int checkarea(int maplayout_factor, char *maplayout); -extern int MA; -extern float Mh; - -bool bad_map() // this function makes a pair with good_map from clients2c -{ - return (gamemode != GMODE_COOPEDIT && ( Mh >= MAXMHEIGHT || MA >= MAXMAREA )); -} - inline const char * spawn_message() { if (spawnpermission == SP_WRONGMAP) @@ -839,11 +817,7 @@ int waiting_permission = 0; bool tryrespawn() { - if(m_mp(gamemode) && multiplayer(NULL) && bad_map()) - { - hudoutf("This map is not supported in multiplayer. Read the docs about map quality/dimensions."); - } - else if(spawnpermission > SP_OK_NUM) + if(spawnpermission > SP_OK_NUM) { hudeditf(HUDMSG_TIMER, "\f%s", spawn_message()); } @@ -930,8 +904,6 @@ void dodamage(int damage, playerent *pl, playerent *actor, int gun, bool gib, bo { if(pl->state != CS_ALIVE || intermission) return; pl->lastpain = lastmillis; - // could the author of the FIXME below please elaborate what's to fix?! (ft:2011mar28) - // I suppose someone wanted to play the hitsound for player1 or spectated player (lucas:2011may22) playerent *h = player1->isspectating() && player1->followplayercn >= 0 && (player1->spectatemode == SM_FOLLOW1ST || player1->spectatemode == SM_FOLLOW3RD || player1->spectatemode == SM_FOLLOW3RD_TRANSPARENT) ? getclient(player1->followplayercn) : NULL; if(!h) h = player1; exechook(HOOK_SP, "onHit", "%d %d %d %d %d", actor->clientnum, pl->clientnum, damage, gun, gib ? 1 : 0); @@ -969,10 +941,9 @@ void dokill(playerent *pl, playerent *act, bool gib, int gun) exechook(HOOK_SP, "onKill", "%d %d %d %d", act->clientnum, pl->clientnum, gun, gib ? 1 : 0); - string pname, aname, death; - copystring(pname, pl==player1 ? "you" : colorname(pl)); - copystring(aname, act==player1 ? "you" : colorname(act)); - copystring(death, killmessage(gun, gib)); + const char *pname = pl == player1 ? "you" : colorname(pl); + const char *aname = act == player1 ? "you" : colorname(act); + const char *death = killmessage(gun, gib); void (*outf)(const char *s, ...) = (pl == player1 || act == player1) ? hudoutf : conoutf; if(pl==act) @@ -1176,29 +1147,12 @@ void resetmap(bool mrproper) } int suicided = -1; -extern bool good_map(); -extern bool item_fail; -extern int MA, F2F, Ma, Hhits; -extern float Mh; - -VARP(mapstats_hud, 0, 0, 1); - -void showmapstats() -{ - conoutf("\f2Map Quality Stats"); - conoutf(" The mean height is: %.2f", Mh); - if (Hhits) conoutf(" Height check is: %d", Hhits); - if (MA) conoutf(" The max area is: %d (of %d)", MA, Ma); - if (m_flags && F2F < 1000) conoutf(" Flag-to-flag distance is: %d", (int)sqrtf(F2F)); - if (item_fail) conoutf(" There are one or more items too close to each other in this map"); -} -COMMAND(showmapstats, ""); VARP(showmodedescriptions, 0, 1, 1); -extern bool canceldownloads; void startmap(const char *name, bool reset, bool norespawn) // called just after a map load { + extern bool canceldownloads; canceldownloads = false; copystring(clientmap, name); @@ -1212,16 +1166,13 @@ void startmap(const char *name, bool reset, bool norespawn) // called just aft suicided = -1; spawncycle = -1; lasthit = 0; - player1->followplayercn = -1; if(m_valid(gamemode) && !m_mp(gamemode)) respawnself(); else findplayerstart(player1); - if(good_map()==MAP_IS_BAD) conoutf("You cannot play in this map due to quality requisites. Please, report this incident."); - if (mapstats_hud) showmapstats(); if(!reset) return; - player1->frags = player1->flagscore = player1->deaths = player1->lifesequence = player1->points = player1->tks = 0; - loopv(players) if(players[i]) players[i]->frags = players[i]->flagscore = players[i]->deaths = players[i]->lifesequence = players[i]->points = players[i]->tks = 0; + player1->startmap(); + loopv(players) if(players[i]) players[i]->startmap(); if(editmode) toggleedit(true); intermission = false; showscores(false); @@ -1297,6 +1248,7 @@ void flagmsg(int flag, int message, int actor, int flagtime) } else hudoutf("\f2%s%s has %s flag", flagteam, colorname(act), teamstr); break; + case FM_LOST: case FM_DROP: { @@ -1315,6 +1267,7 @@ void flagmsg(int flag, int message, int actor, int flagtime) if(firstperson) hudoutf("\f2you returned your flag"); else hudoutf("\f2%s returned %s flag", colorname(act), teamstr); break; + case FM_SCORE: audiomgr.playsound(S_FLAGSCORE, SP_HIGHEST); if(firstperson) @@ -1324,6 +1277,7 @@ void flagmsg(int flag, int message, int actor, int flagtime) } else hudoutf("\f2%s scored for %s", colorname(act), neutral ? teamnames[act->team] : teammate ? "your team" : "the enemy team"); break; + case FM_KTFSCORE: { audiomgr.playsound(S_KTFSCORE, SP_HIGHEST); @@ -1340,6 +1294,7 @@ void flagmsg(int flag, int message, int actor, int flagtime) case FM_SCOREFAIL: // sound? hudoutf("\f2%s failed to score (own team flag not taken)", firstperson ? "you" : colorname(act)); break; + case FM_RESET: audiomgr.playsound(S_FLAGRETURN, SP_HIGHEST); hudoutf("the server reset the flag"); @@ -1646,13 +1601,11 @@ void cleanplayervotes(playerent *p) void whois(int *cn) { + string tmp; loopv(players) if(players[i] && players[i]->type == ENT_PLAYER && (*cn == -1 || players[i]->clientnum == *cn)) { playerent *p = players[i]; - uint2ip(p->address, ip); - if(ip[3] != 0 || player1->clientrole==CR_ADMIN) - conoutf("WHOIS client %d:\tname %s , IP %d.%d.%d.%d , %d teamkill%s", p->clientnum, colorname(p), ip[0], ip[1], ip[2], ip[3], p->tks, p->tks == 1 ? "" : "s"); // full IP - else conoutf("WHOIS client %d:\tname %s , IP %d.%d.%d.x , %d teamkill%s", p->clientnum, colorname(p), ip[0], ip[1], ip[2], p->tks, p->tks == 1 ? "" : "s"); // censored IP + conoutf("WHOIS client %d:\tname %s , IP %s , %d teamkill%s", p->clientnum, colorname(p), iptoa(p->address, tmp), p->tks, p->tks == 1 ? "" : "s"); // full IP } } COMMAND(whois, "i"); @@ -1681,7 +1634,6 @@ void setadmin(int *claim, char *password) else if(*claim != 0 && *password) addmsg(SV_SETADMIN, "ris", *claim, genpwdhash(player1->name, password, sessionid)); } - COMMAND(setadmin, "is"); static vector mlines; @@ -1729,6 +1681,7 @@ playerent *updatefollowplayer(int shiftdirection) if(oldidx < 0) oldidx = 0; int idx = ((oldidx + shiftdirection) % available.length() + available.length()) % available.length(); + if(player1->followplayercn != available[idx]->clientnum) addmsg(SV_SPECTCN, "ri", available[idx]->clientnum); player1->followplayercn = available[idx]->clientnum; return players[player1->followplayercn]; } @@ -1739,7 +1692,6 @@ void spectate() if(!team_isspect(player1->team)) addmsg(SV_SWITCHTEAM, "ri", TEAM_SPECT); else tryrespawn(); } - COMMAND(spectate, ""); void setfollowplayer(int cn) @@ -1749,12 +1701,11 @@ void setfollowplayer(int cn) { if(!(m_teammode && player1->team != TEAM_SPECT && !watchingdemo && team_base(players[cn]->team) != team_base(player1->team))) { - player1->followplayercn = cn; + if(player1->followplayercn != cn) addmsg(SV_SPECTCN, "ri", (player1->followplayercn = cn)); if(player1->spectatemode == SM_FLY) player1->spectatemode = SM_FOLLOW1ST; } } } - COMMANDF(setfollowplayer, "i", (int *cn) { setfollowplayer(*cn); }); // set new spect mode @@ -1785,16 +1736,17 @@ void spectatemode(int mode) player1->resetinterp(); } else entinmap(player1); // or drop 'em at a random place + addmsg(SV_SPECTCN, "ri", (player1->followplayercn = FPCN_FLY)); } break; } case SM_OVERVIEW: + addmsg(SV_SPECTCN, "ri", (player1->followplayercn = FPCN_OVERVIEW)); break; default: break; } player1->spectatemode = mode; } - COMMANDF(spectatemode, "i", (int *mode) { spectatemode(*mode); }); void togglespect() // cycle through all spectating modes @@ -1809,15 +1761,13 @@ void togglespect() // cycle through all spectating modes spectatemode(mode); } } - COMMAND(togglespect, ""); -void changefollowplayer(int shift) +void changefollowplayer(int *shift) { - if(!m_botmode) updatefollowplayer(shift); + if(!m_botmode) updatefollowplayer(*shift); } - -COMMANDF(changefollowplayer, "i", (int *dir) { changefollowplayer(*dir); }); +COMMAND(changefollowplayer, "i"); void spectatecn() { @@ -1826,7 +1776,6 @@ void spectatecn() spectcn = player1->followplayercn; intret(spectcn); } - COMMAND(spectatecn, ""); void serverextension(char *ext, char *args) @@ -1836,5 +1785,4 @@ void serverextension(char *ext, char *args) if(n>0) addmsg(SV_EXTENSION, "rsis", ext, n, args); else addmsg(SV_EXTENSION, "rsi", ext, n); } - COMMAND(serverextension, "ss"); diff --git a/source/src/clients2c.cpp b/source/src/clients2c.cpp index 62e3d267..47b3a301 100644 --- a/source/src/clients2c.cpp +++ b/source/src/clients2c.cpp @@ -33,10 +33,9 @@ extern bool incompatiblemap; extern void trydisconnect(); bool localwrongmap = false; -int MA = 0, Hhits = 0; // flowtron: moved here + bool changemapserv(char *name, int mode, int download, int revision) // forced map change from the server { - MA = Hhits = 0; // reset for checkarea() gamemode = mode; if(m_demo) return true; if(m_coop) @@ -111,8 +110,6 @@ void updatelagtime(playerent *d) } } -extern void trydisconnect(); - void parsepositions(ucharbuf &p) { int type; @@ -120,89 +117,81 @@ void parsepositions(ucharbuf &p) { case SV_POS: // position of another client case SV_POSC: + case SV_POSC2: + case SV_POSC3: + case SV_POSC4: { - int cn, f, g; + int cn, f, yaw, pitch, z; vec o, vel; - float yaw, pitch = 0; - bool scoping;//, shoot; - if(type == SV_POSC) + if(type != SV_POS) { bitbuf q(p); cn = q.getbits(5); - int usefactor = q.getbits(2) + 7; + int usefactor = type - SV_POSC + 7; o.x = q.getbits(usefactor + 4) / DMF; o.y = q.getbits(usefactor + 4) / DMF; - yaw = q.getbits(9) * 360.0f / 512; - pitch = (q.getbits(8) - 128) * 90.0f / 127; - if(!q.getbits(1)) q.getbits(6); - if(!q.getbits(1)) + yaw = q.getbits(YAWBITS); + pitch = q.getbits(PITCHBITS); + f = q.getbits(FLAGBITS); + if(f & (1 << 9)) { vel.x = (q.getbits(4) - 8) / DVELF; vel.y = (q.getbits(4) - 8) / DVELF; vel.z = (q.getbits(4) - 8) / DVELF; } else vel.x = vel.y = vel.z = 0.0f; - f = q.getbits(8); - int negz = q.getbits(1); int full = q.getbits(1); int s = q.rembits(); if(s < 3) s += 8; if(full) s = 11; - int z = q.getbits(s); - if(negz) z = -z; - o.z = z / DMF; - scoping = ( q.getbits(1) ? true : false ); - q.getbits(1);//shoot = ( q.getbits(1) ? true : false ); + z = q.getbits(s); } else { cn = getint(p); - o.x = getuint(p)/DMF; - o.y = getuint(p)/DMF; - o.z = getuint(p)/DMF; - yaw = (float)getuint(p); - pitch = (float)getint(p); - g = getuint(p); - if ((g>>3) & 1) getint(p); - if (g & 1) vel.x = getint(p)/DVELF; else vel.x = 0; - if ((g>>1) & 1) vel.y = getint(p)/DVELF; else vel.y = 0; - if ((g>>2) & 1) vel.z = getint(p)/DVELF; else vel.z = 0; - scoping = ( (g>>4) & 1 ? true : false ); - //shoot = ( (g>>5) & 1 ? true : false ); // we are not using this yet - f = getuint(p); + o.x = getuint(p)/DMF; + o.y = getuint(p)/DMF; + z = getuint(p); + uint64_t ff = getuintn(p, (YAWBITS + PITCHBITS + FLAGBITS + 7) / 8); + yaw = (ff >> (FLAGBITS + PITCHBITS)) & ((1 << YAWBITS) - 1); + pitch = (ff >> FLAGBITS) & ((1 << PITCHBITS) - 1); + f = ff & ((1 << FLAGBITS) - 1); + if(f & (1 << 9)) + { + vel.x = getint(p)/DVELF; + vel.y = getint(p)/DVELF; + vel.z = getint(p)/DVELF; + } + else vel.x = vel.y = vel.z = 0.0f; } + if(f & (1 << 10)) z = -z; + o.z = z / DMF; int seqcolor = (f>>6)&1; playerent *d = getclient(cn); if(!d || seqcolor!=(d->lifesequence&1)) continue; vec oldpos(d->o); float oldyaw = d->yaw, oldpitch = d->pitch; - loopi(3) - { - float dr = o.v[i] - d->o.v[i] + ( i == 2 ? d->eyeheight : 0); - if ( !dr ) d->vel.v[i] = 0.0f; - else if ( d->vel.v[i] ) d->vel.v[i] = dr * 0.05f + d->vel.v[i] * 0.95f; - d->vel.v[i] += vel.v[i]; - if ( i==2 && d->onfloor && d->vel.v[i] < 0.0f ) d->vel.v[i] = 0.0f; - } d->o = o; d->o.z += d->eyeheight; - d->yaw = yaw; - d->pitch = pitch; + d->yaw = decodeyaw(yaw); + d->pitch = decodepitch(pitch); if(d->weaponsel->type == GUN_SNIPER) { sniperrifle *sr = (sniperrifle *)d->weaponsel; - sr->scoped = d->scoping = scoping; + sr->scoped = d->scoping = (f & (1 << 5)) ? true : false; } - d->strafe = (f&3)==3 ? -1 : f&3; - f >>= 2; - d->move = (f&3)==3 ? -1 : f&3; - f >>= 2; - d->onfloor = f&1; - f >>= 1; - d->onladder = f&1; - f >>= 2; + d->roll = 0; + d->vel = vel; + int ft = f & 0x1f; + d->strafe = (ft % 3) - 1; + ft /= 3; + d->move = (ft % 3) - 1; + ft /= 3; + d->jumpd = (f & (1 << 8)) ? true : false; // FIXME + d->onfloor = f & (1 << 7) ? true : false; + d->onladder = ft == 1; d->last_pos = totalmillis; - updatecrouch(d, f&1); + updatecrouch(d, ft == 2); updateplayerpos(d); updatelagtime(d); extern int smoothmove, smoothdist; @@ -241,138 +230,6 @@ void parsepositions(ucharbuf &p) } } -extern int checkarea(int maplayout_factor, char *maplayout); -char *mlayout = NULL; -int Mv = 0, Ma = 0, F2F = 1000 * MINFF; // moved up:, MA = 0; -float Mh = 0; -extern int connected; -extern bool noflags; -bool item_fail = false; -int map_quality = MAP_IS_EDITABLE; - -/// TODO: many functions and variables are redundant between client and server... someone should redo the entire server code and unify client and server. -bool good_map() // call this function only at startmap -{ - return true; - if (mlayout) MA = checkarea(sfactor, mlayout); - - F2F = 1000 * MINFF; - if(m_flags) - { -// flaginfo &f0 = flaginfos[0]; - // flaginfo &f1 = flaginfos[1]; -#define DIST(x) (f0.pos.x - f1.pos.x) - F2F = 1000;//(!numflagspawn[0] || !numflagspawn[1]) ? 1000 * MINFF : DIST(x)*DIST(x)+DIST(y)*DIST(y); -#undef DIST - } - - item_fail = false; - loopv(ents) - { - entity &e1 = ents[i]; - if (e1.type < I_CLIPS || e1.type > I_AKIMBO) continue; - float density = 0, hdensity = 0; - loopvj(ents) - { - entity &e2 = ents[j]; - if (e2.type < I_CLIPS || e2.type > I_AKIMBO || i == j) continue; - // only I_CLIPS, I_AMMO, I_GRENADE, I_HEALTH, I_HELMET, I_ARMOUR, I_AKIMBO -#define DIST(x) (e1.x - e2.x) -#define DIST_ATT ((e1.z + float(e1.attr1) / entscale[e1.type][0]) - (e2.z + float(e2.attr1) / entscale[e2.type][0])) - float r2 = DIST(x)*DIST(x) + DIST(y)*DIST(y) + DIST_ATT*DIST_ATT; -#undef DIST_ATT -#undef DIST - if ( r2 == 0.0f ) { conoutf("\f3MAP CHECK FAIL: Items too close %s %s (%hd,%hd)", entnames[e1.type], entnames[e2.type],e1.x,e1.y); item_fail = true; break; } - r2 = 1/r2; - if (r2 < 0.0025f) continue; - if (e1.type != e2.type) - { - hdensity += r2; - continue; - } - density += r2; - } - if ( hdensity > 0.5f ) { conoutf("\f3MAP CHECK FAIL: Items too close %s %.2f (%hd,%hd)", entnames[e1.type],hdensity,e1.x,e1.y); item_fail = true; break; } - switch(e1.type) - { -#define LOGTHISSWITCH(X) if( density > X ) { conoutf("\f3MAP CHECK FAIL: Items too close %s %.2f (%hd,%hd)", entnames[e1.type],density,e1.x,e1.y); item_fail = true; break; } - case I_CLIPS: - case I_HEALTH: LOGTHISSWITCH(0.24f); break; - case I_AMMO: LOGTHISSWITCH(0.04f); break; - case I_HELMET: LOGTHISSWITCH(0.02f); break; - case I_ARMOUR: - case I_GRENADE: - case I_AKIMBO: LOGTHISSWITCH(0.005f); break; - default: break; -#undef LOGTHISSWITCH - } - } - - map_quality = (!item_fail && F2F > MINFF && MA < MAXMAREA && Mh < MAXMHEIGHT && Hhits < MAXHHITS) ? MAP_IS_GOOD : MAP_IS_BAD; - if ( (!connected || gamemode == GMODE_COOPEDIT) && map_quality == MAP_IS_BAD ) map_quality = MAP_IS_EDITABLE; - return map_quality > 0; -} - -VARP(hudextras, 0, 0, 3); - -int teamworkid = -1; - -char *strcaps(const char *s, bool on) -{ - static string r; - char *o = r; - if(on) while(*s && o < &r[sizeof(r)-1]) *o++ = toupper(*s++); - else while(*s && o < &r[sizeof(r)-1]) *o++ = tolower(*s++); - *o = '\0'; - return r; -} - -void showhudextras(char hudextras, char value){ - void (*outf)(const char *s, ...) = (hudextras > 1 ? hudoutf : conoutf); - bool caps = hudextras < 3 ? false : true; - switch(value) - { - case HE_COMBO: - case HE_COMBO2: - case HE_COMBO3: - case HE_COMBO4: - case HE_COMBO5: - { - int n = value - HE_COMBO; - if (n > 3) outf("\f3%s",strcaps("monster combo!!!",caps)); // I expect to never see this one - else if (!n) outf("\f5%s",strcaps("combo", caps)); - else outf("\f5%s x%d",strcaps("multi combo", caps),n+1); - break; - } - case HE_TEAMWORK: - outf("\f5%s",strcaps("teamwork done", caps)); break; - case HE_FLAGDEFENDED: - outf("\f5%s",strcaps("you defended the flag", caps)); break; - case HE_FLAGCOVERED: - outf("\f5%s",strcaps("you covered the flag", caps)); break; - case HE_COVER: - if (teamworkid >= 0) - { - playerent *p = getclient(teamworkid); - if (!p || p == player1) teamworkid = -1; - else outf("\f5you covered %s",p->name); break; - } - default: - { - if (value >= HE_NUM) - { - teamworkid = value - HE_NUM; - playerent *p = getclient(teamworkid); - if (!p || p == player1) teamworkid = -1; - else outf("\f4you replied to %s",p->name); - } - else outf("\f3Update your client!"); - break; - } - } -#undef SSPAM -} - int lastspawn = 0; void onCallVote(int type, int vcn, char *text, char *a) @@ -386,8 +243,17 @@ void onChangeVote(int mod, int id) } VARP(voicecomsounds, 0, 1, 2); -bool medals_arrived=0; -medalsst a_medals[END_MDS]; + +struct session_s +{ + enet_uint32 serverip, clientip, clientipcensored, curpeerip; + int curpeerport, serverclock, clientclock, cn, clientsalt, datecodes; + uchar serverpubkey[32], clientsignature[64]; + string clan, publiccomment; +} session; + +int srvgamesalt; + void parsemessages(int cn, playerent *d, ucharbuf &p, bool demo = false) { static char text[MAXTRANS]; @@ -397,22 +263,10 @@ void parsemessages(int cn, playerent *d, ucharbuf &p, bool demo = false) { type = getint(p); - if(demo && watchingdemo && demoprotocol == 1132) - { - if(type > SV_IPLIST) --type; // SV_WHOIS removed - if(type >= SV_TEXTPRIVATE) ++type; // SV_TEXTPRIVATE added - if(type == SV_SWITCHNAME) // SV_SPECTCN removed - { - getint(p); - continue; - } - else if(type > SV_SWITCHNAME) --type; - } - #ifdef _DEBUG if(type!=SV_POS && type!=SV_CLIENTPING && type!=SV_PING && type!=SV_PONG && type!=SV_CLIENT) { - DEBUGVAR(d); + DEBUGVAR(cn); ASSERT(type>=0 && typecn = getint(p); + int prot = getint(p); if(prot!=CUR_PROTOCOL_VERSION && !(watchingdemo && prot == -PROTOCOL_VERSION)) { conoutf("\f3incompatible game protocol (local protocol: %d :: server protocol: %d)", CUR_PROTOCOL_VERSION, prot); @@ -433,9 +292,96 @@ void parsemessages(int cn, playerent *d, ucharbuf &p, bool demo = false) else disconnect(); return; } - sessionid = getint(p); - player1->clientnum = mycn; + int req_auth = getint(p); + if(!sk && req_auth) + { + conoutf("\f3server requires client ID to connect"); + conoutf("\f3please unlock your ID (by entering the corect password) or generate a new one"); + disconnect(); + return; + } if(getint(p) > 0) conoutf("INFO: this server is password protected"); + sessionid = getint(p); // salt + s->serverip = getip4(p); + s->clientip = getip4(p); + s->clientipcensored = getip4(p); + s->serverclock = getint(p); + char cc[3] = { (char)getint(p), (char)getint(p), 0 }; + filtercountrycode(cc, cc); + p.get(s->serverpubkey, 32); + entropy_get((uchar *)&(s->clientsalt), sizeof(int)); + s->clientclock = (int) (time(NULL) / (time_t) 60); + int clockoffset = s->serverclock - s->clientclock; + if(curpeer && iabs(clockoffset) > 60 * 24) conoutf("\f3warning: server and client clock are offset more than one day (%d minutes)", clockoffset); + + clientlogf("own IP: %s, censored own IP: %s, %s, clock offset %d hours %d minutes", iptoa(s->clientip, tmp1), iptoa(s->clientipcensored, tmp2), cc, clockoffset / 60, clockoffset % 60); + if(curpeer && s->serverip) + { + s->curpeerip = ENET_NET_TO_HOST_32(curpeer->address.host); + s->curpeerport = curpeer->address.port; + } + if(s->curpeerip && s->curpeerip != s->serverip) conoutf("\f3warning: server IP mismatch (true: %s, reported: %s)", iptoa(s->curpeerip, tmp1), iptoa(s->serverip, tmp2)); + + defformatstring(challenge)("SERVINFOCHALLENGE<(%d) cn: %d c: %s (%s) s: %s:%d", sessionid, s->cn, iptoa(s->clientipcensored, tmp1), cc, iptoa(s->serverip, tmp2), s->curpeerport); + concatformatstring(challenge, " %s st: %d ct: %d (%d)>", bin2hex(tmp1, s->serverpubkey, 32), s->serverclock, s->clientclock, s->clientsalt); + clientlogf("auth challenge: %s", challenge); + int chlen = (int)strlen(challenge); + if(chlen >= MAXSTRLEN - 65) + { + conoutf("\f3ERROR: oversized auth challenge (%d), can't connect", chlen); + disconnect(); + return; + } + if(sk) ed25519_sign((uchar*)challenge, NULL, (uchar*)challenge, chlen, sk); + + packetbuf pr(MAXTRANS, ENET_PACKET_FLAG_RELIABLE); + putint(pr, SV_SERVINFO_RESPONSE); + putint(pr, s->clientsalt); + putint(pr, s->clientclock); + putint(pr, s->curpeerport); + putip4(pr, s->curpeerip); + int wantauth = sk && curpeer; + putint(pr, wantauth); + if(wantauth) + { + memcpy(s->clientsignature, challenge, 64); + pr.put(sk + 32, 32); + pr.put(s->clientsignature, 64); + } + sendpackettoserv(1, pr.finalize()); + break; + } + + case SV_SERVINFO_CONTD: // 2nd welcome message from the server + { + session_s *s = &session; + string tmp, servauth; + int auth = getint(p); + if(auth) + { + formatstring(servauth)("SERVINFOSIGNED<%s>", bin2hex(tmp, (uchar*)s->clientsignature, 64)); + memmove(servauth + 64, servauth, (auth = (int)strlen(servauth)) + 1); + p.get((uchar*)servauth, 64); + s->datecodes = getint(p); + getstring(text, p); + filtertext(s->clan, text, FTXT__VITACLAN); + getstring(text, p); + filtertext(s->publiccomment, text, FTXT__VITACOMMENT); +#ifdef _DEBUG + formatstring(text)("server-side%s", s->datecodes ? " timed flags:" : ""); + loopi(VS_NUMCOUNTERS) if(s->datecodes & (1 << i)) concatformatstring(text, " %s", vskeywords[i]); + if(s->clan[0]) concatformatstring(text, " clan tag: \"%s\"", s->clan); + if(s->publiccomment[0]) concatformatstring(text, " comment: \"%s\"", s->publiccomment); + if(strlen(text) > 12) conoutf("%s", text); +#endif + } + if(curpeer && sk && !auth) conoutf("\f3server refuses to authenticate"); + if(sk && auth) + { + if(!ed25519_sign_check((uchar*)servauth, auth + 64, s->serverpubkey)) conoutf("\f3server authentication failed"); + else clientlogf("server authenticated as %s", bin2hex(tmp, s->serverpubkey, 32)); + } + player1->clientnum = s->cn; sendintro(); break; } @@ -517,17 +463,18 @@ void parsemessages(int cn, playerent *d, ucharbuf &p, bool demo = false) case SV_TEXTPRIVATE: { - int cn = getint(p); + int src = getint(p), dst = getint(p); getstring(text, p); filtertext(text, text, FTXT__CHAT); - playerent *d = getclient(cn); - if(!d) break; - if(d->ignored) clientlogf("ignored: pm %s %s", colorname(d), text); + playerent *s = getclient(src), *d = getclient(dst); + if(!s) break; + if(s == player1 && d) conoutf("to %s (PM):\f9 %s", colorname(d), highlight(text)); + else if(s->ignored) clientlogf("ignored: pm %s %s", colorname(s), text); else { - conoutf("%s (PM):\f9 %s", colorname(d), highlight(text)); - lastpm = d->clientnum; - exechook(HOOK_SP_MP, "onPM", "%d [%s]", d->clientnum, text); + conoutf("%s (PM):\f9 %s", colorname(s), highlight(text)); + lastpm = s->clientnum; + exechook(HOOK_SP_MP, "onPM", "%d [%s]", s->clientnum, text); } break; } @@ -540,6 +487,7 @@ void parsemessages(int cn, playerent *d, ucharbuf &p, bool demo = false) int mode = getint(p); int downloadable = getint(p); int revision = getint(p); + srvgamesalt = getint(p); localwrongmap = !changemapserv(text, mode, downloadable, revision); if(m_arena && joining > 1 && !watchingdemo) deathstate(player1); break; @@ -593,7 +541,8 @@ void parsemessages(int cn, playerent *d, ucharbuf &p, bool demo = false) getstring(text, p); loopi(2) getint(p); getint(p); - if(!demo || !watchingdemo || demoprotocol > 1132) getint(p); + loopi(4) getint(p); + getint(p); break; } getstring(text, p); @@ -613,10 +562,11 @@ void parsemessages(int cn, playerent *d, ucharbuf &p, bool demo = false) loopi(2) d->setskin(i, getint(p)); d->team = getint(p); -// d->maxroll = (float)clamp(getint(p), 0, ROLLMOVMAX); FIXME: uncomment on protocol bump + etc. -// d->maxrolleffect = (float)clamp(getint(p), 0, ROLLEFFMAX); FIXME: uncomment on protocol bump - - if(!demo || !watchingdemo || demoprotocol > 1132) d->address = getint(p); // partial IP address + d->maxroll = (float)clamp(getint(p), 0, ROLLMOVMAX); + d->maxrolleffect = (float)clamp(getint(p), 0, ROLLEFFMAX); + d->ffov = (float)clamp(getint(p), 75, 120); + d->scopefov = (float)clamp(getint(p), 5, 60); + d->address = getint(p); // partial IP address if(m_flags) loopi(2) { @@ -668,17 +618,9 @@ void parsemessages(int cn, playerent *d, ucharbuf &p, bool demo = false) case SV_SPAWNSTATE: { - if ( map_quality == MAP_IS_BAD ) - { - loopi(6+2*NUMGUNS) getint(p); - conoutf("map deemed unplayable - fix it before you can spawn"); - break; - } - if(editmode) toggleedit(true); showscores(false); setscope(false); - setburst(false); player1->respawn(); player1->lifesequence = getint(p); player1->health = getint(p); @@ -694,7 +636,7 @@ void parsemessages(int cn, playerent *d, ucharbuf &p, bool demo = false) arenaintermission = 0; if(m_arena && !localwrongmap) { - if(connected) closemenu(NULL); + if(curpeer) closemenu(NULL); conoutf("new round starting... fight!"); hudeditf(HUDMSG_TIMER, "FIGHT!"); if(m_botmode) BotManager.RespawnBots(); @@ -706,6 +648,10 @@ void parsemessages(int cn, playerent *d, ucharbuf &p, bool demo = false) break; } + case SV_SPECTCN: + getint(p); + break; + case SV_SHOTFX: { int scn = getint(p), gun = getint(p); @@ -731,9 +677,9 @@ void parsemessages(int cn, playerent *d, ucharbuf &p, bool demo = false) case SV_THROWNADE: { - vec from, to; + vec from, vel; loopk(3) from[k] = getint(p)/DMF; - loopk(3) to[k] = getint(p)/DMF; + loopk(3) vel[k] = getint(p)/DNF; int nademillis = getint(p); if(!d) break; d->lastaction = lastmillis; @@ -741,7 +687,7 @@ void parsemessages(int cn, playerent *d, ucharbuf &p, bool demo = false) d->lastattackweapon = d->weapons[GUN_GRENADE]; if(d->weapons[GUN_GRENADE]) { - d->weapons[GUN_GRENADE]->attackfx(from, to, nademillis); + d->weapons[GUN_GRENADE]->attackfx(from, vel, nademillis); d->weapons[GUN_GRENADE]->reloading = 0; } if(d!=player1) d->pstatshots[GUN_GRENADE]++; //NEW @@ -756,34 +702,6 @@ void parsemessages(int cn, playerent *d, ucharbuf &p, bool demo = false) break; } - // for AUTH: WIP - - case SV_AUTHREQ: - { -// extern int autoauth; - getstring(text, p); - // if(autoauth && text[0] && tryauth(text)) conoutf("server requested authkey \"%s\"", text); - break; - } - - case SV_AUTHCHAL: - { - getstring(text, p); - // authkey *a = findauthkey(text); - // uint id = (uint)getint(p); - getstring(text, p); - // if(a && a->lastauth && lastmillis - a->lastauth < 60*1000) - { - // vector buf; - // answerchallenge(a->key, text, buf); - //conoutf("answering %u, challenge %s with %s", id, text, buf.getbuf()); - // addmsg(SV_AUTHANS, "rsis", a->desc, id, buf.getbuf()); - } - break; - } - - // :for AUTH - case SV_GIBDAMAGE: case SV_DAMAGE: { @@ -802,38 +720,6 @@ void parsemessages(int cn, playerent *d, ucharbuf &p, bool demo = false) break; } - case SV_POINTS: - { - int count = getint(p); - if ( count > 0 ) { - loopi(count){ - int pcn = getint(p); int score = getint(p); - playerent *ppl = getclient(pcn); - if (!ppl) break; - ppl->points += score; - } - } else { - int medals = getint(p); - if(medals > 0) { -// medals_arrived=1; - loopi(medals) { - int mcn=getint(p); int mtype=getint(p); int mitem=getint(p); - a_medals[mtype].assigned=1; - a_medals[mtype].cn=mcn; - a_medals[mtype].item=mitem; - } - } - } - break; - } - - case SV_HUDEXTRAS: - { - char value = getint(p); - if (hudextras) showhudextras(hudextras, value); - break; - } - case SV_HITPUSH: { int gun = getint(p), damage = getint(p); @@ -861,9 +747,17 @@ void parsemessages(int cn, playerent *d, ucharbuf &p, bool demo = false) { int cn = getint(p); if(p.overread() || cn<0) break; - int state = getint(p), lifesequence = getint(p), primary = getint(p), gunselect = getint(p), flagscore = getint(p), frags = getint(p), deaths = getint(p), health = getint(p), armour = getint(p), points = getint(p); - int teamkills = 0; - if(!demo || !watchingdemo || demoprotocol > 1132) teamkills = getint(p); + int state = getint(p), + lifesequence = getint(p), + primary = getint(p), + gunselect = getint(p), + flagscore = getint(p), + frags = getint(p), + deaths = getint(p), + health = getint(p), + armour = getint(p), + points = getint(p), + teamkills = getint(p); int ammo[NUMGUNS], mag[NUMGUNS]; loopi(NUMGUNS) ammo[i] = getint(p); loopi(NUMGUNS) mag[i] = getint(p); @@ -1037,11 +931,6 @@ void parsemessages(int cn, playerent *d, ucharbuf &p, bool demo = false) d->ping = getint(p); break; - case SV_GAMEMODE: - nextmode = getint(p); - if (nextmode >= GMODE_NUM) nextmode -= GMODE_NUM; - break; - case SV_TIMEUP: { int curgamemillis = getint(p); @@ -1057,6 +946,7 @@ void parsemessages(int cn, playerent *d, ucharbuf &p, bool demo = false) break; } + case SV_SERVMSGVERB: // FIXME case SV_SERVMSG: getstring(text, p); conoutf("%s", text); @@ -1375,12 +1265,22 @@ void parsemessages(int cn, playerent *d, ucharbuf &p, bool demo = false) demo_mlines.reserve(demos); loopi(demos) { - getstring(text, p); - conoutf("%d. %s", i+1, text); + int seq = getint(p); + getstring(text, p); // info + filtertext(text, text, FTXT__DEMOINFO, MAXMAPNAMELEN + 64); + int len = getint(p); // len + concatformatstring(text, ", %.2f%s", len > 1024*1024 ? len/(1024*1024.f) : len/1024.0f, len > 1024*1024 ? "MB" : "kB"); + conoutf("%d. %s", seq, text); mline &m = demo_mlines.add(); - formatstring(m.name)("%d. %s", i+1, text); - formatstring(m.cmd)("getdemo %d", i+1); + formatstring(m.name)("%d. %s", seq, text); + formatstring(m.cmd)("getdemo %d", seq); menuitemmanual(downloaddemomenu, m.name, m.cmd); + getstring(text, p); // mapname + filtertext(text, text, FTXT__MAPNAME, MAXMAPNAMELEN); + getint(p); // gmode + getint(p); // timeplayed + getint(p); // timeremain + getint(p); // timestamp } } break; @@ -1391,16 +1291,8 @@ void parsemessages(int cn, playerent *d, ucharbuf &p, bool demo = false) bool demoplayback = false; string demofile; extern char *curdemofile; - if(demo && watchingdemo && demoprotocol == 1132) - { - watchingdemo = demoplayback = getint(p)!=0; - copystring(demofile, "n/a"); - } - else - { - getstring(demofile, p, MAXSTRLEN); - watchingdemo = demoplayback = demofile[0] != '\0'; - } + getstring(demofile, p, MAXSTRLEN); + watchingdemo = demoplayback = demofile[0] != '\0'; DELETEA(curdemofile); if(demoplayback) { @@ -1422,6 +1314,33 @@ void parsemessages(int cn, playerent *d, ucharbuf &p, bool demo = false) break; } + case SV_DEMOCHECKSUM: // raw data checksum of demo file + { + uchar dtiger[TIGERHASHSIZE]; + if(p.get(dtiger, TIGERHASHSIZE) == TIGERHASHSIZE) + { + bin2hex(text, dtiger, TIGERHASHSIZE); + defformatstring(msg)("CLIENTDEMOEND<%s %s %d>", msg, iptoa(session.curpeerip, text + 100), session.curpeerport); + int msglen = (int)strlen(msg); + ASSERT(msglen + 64 < MAXSTRLEN); + if(sk) ed25519_sign((uchar*)msg, NULL, (uchar*)msg, msglen, sk); + else memset(msg, 0, 64); + + packetbuf pr(MAXTRANS, ENET_PACKET_FLAG_RELIABLE); + putint(pr, SV_DEMOSIGNATURE); + putint(pr, session.curpeerport); + putip4(pr, session.curpeerip); + pr.put((uchar*)msg, 64); + sendpackettoserv(1, pr.finalize()); + } + break; + } + case SV_DEMOSIGNATURE: // client acknowledging a demo checksum + getint(p); + getip4(p); + p.get((uchar*)text, 64); + break; + default: neterr("type"); return; @@ -1433,128 +1352,61 @@ void parsemessages(int cn, playerent *d, ucharbuf &p, bool demo = false) #endif } -void setDemoFilenameFormat(char *fmt) -{ - extern string demofilenameformat; - if(fmt && fmt[0]!='\0') - { - copystring(demofilenameformat, fmt); - } else copystring(demofilenameformat, DEFDEMOFILEFMT); // reset to default if passed empty string - or should we output the current value in this case? -} -COMMANDN(demonameformat, setDemoFilenameFormat, "s"); -void setDemoTimestampFormat(char *fmt) -{ - extern string demotimestampformat; - if(fmt && fmt[0]!='\0') - { - copystring(demotimestampformat, fmt); - } else copystring(demotimestampformat, DEFDEMOTIMEFMT); // reset to default if passed empty string - or should we output the current value in this case? -} -COMMANDN(demotimeformat, setDemoTimestampFormat, "s"); -void setDemoTimeLocal(int *truth) -{ - extern int demotimelocal; - demotimelocal = *truth == 0 ? 0 : 1; -} -COMMANDN(demotimelocal, setDemoTimeLocal, "i"); -void getdemonameformat() { extern string demofilenameformat; result(demofilenameformat); } COMMAND(getdemonameformat, ""); -void getdemotimeformat() { extern string demotimestampformat; result(demotimestampformat); } COMMAND(getdemotimeformat, ""); -void getdemotimelocal() { extern int demotimelocal; intret(demotimelocal); } COMMAND(getdemotimelocal, ""); - - -const char *parseDemoFilename(char *srvfinfo) -{ - int gmode = 0; //-314; - int mplay = 0; - int mdrop = 0; - int stamp = 0; - string srvmap; - if(srvfinfo && srvfinfo[0]) - { - int fip = 0; - char sep[] = ":"; - char *pch, *b; - pch = strtok_r(srvfinfo, sep, &b); - while (pch != NULL && fip < 4) - { - fip++; - switch(fip) - { - case 1: gmode = atoi(pch); break; - case 2: mplay = atoi(pch); break; - case 3: mdrop = atoi(pch); break; - case 4: stamp = atoi(pch); break; - default: break; - } - pch = strtok_r(NULL, sep, &b); - } - copystring(srvmap, pch); - } - extern const char *getDemoFilename(int gmode, int mplay, int mdrop, int tstamp, char *srvmap); - return getDemoFilename(gmode, mplay, mdrop, stamp, srvmap); -} +SVARP(demonameformat, "%w_%h_%n_%Mmin_%G"); +SVARP(demotimestampformat, "%Y%m%d_%H%M"); void receivefile(uchar *data, int len) { static char text[MAXTRANS]; ucharbuf p(data, len); int type = getint(p); - data += p.length(); - len -= p.length(); switch(type) { case SV_SENDDEMO: { + /* int sequence = */ getint(p); getstring(text, p); - extern string demosubpath; - defformatstring(demofn)("%s", parseDemoFilename(text)); - defformatstring(fname)("demos/%s%s.dmo", demosubpath, demofn); - copystring(demosubpath, ""); - data += strlen(text); + filtertext(text, text, FTXT__MAPNAME, MAXMAPNAMELEN); + int gmode = getint(p); + int timeplayed = getint(p); + int timeremain = getint(p); + int timestamp = getint(p); int demosize = getint(p); - if(p.remaining() < demosize) + if(demosize <= MAXDEMOSENDSIZE && demosize <= p.remaining()) { - p.forceoverread(); - break; + extern string demosubpath; + string buf; + enet_uint32 ip = curpeer ? ntohl(curpeer->address.host) : 0; + defformatstring(fname)("demos/%s%s.dmo", demosubpath, formatdemofilename(demonameformat, demotimestampformat, text, gmode, timestamp, timeplayed, timeremain, ip, buf)); + copystring(demosubpath, ""); + + path(fname); + stream *demo = openfile(fname, "wb"); + if(demo) + { + conoutf("received demo \"%s\"", fname); + demo->write(&p.buf[p.len], demosize); + delete demo; + } + else conoutf("failed writing to \"%s\"", fname); } - path(fname); - stream *demo = openfile(fname, "wb"); - if(!demo) - { - conoutf("failed writing to \"%s\"", fname); - return; - } - conoutf("received demo \"%s\"", fname); - demo->write(&p.buf[p.len], demosize); - delete demo; break; } case SV_RECVMAP: { getstring(text, p); + filtertext(text, text, FTXT__MAPNAME, MAXMAPNAMELEN); conoutf("received map \"%s\" from server, reloading..", text); int mapsize = getint(p); int cfgsize = getint(p); int cfgsizegz = getint(p); - /* int revision = */ getint(p); int size = mapsize + cfgsizegz; - if(MAXMAPSENDSIZE < mapsize + cfgsizegz || cfgsize > MAXCFGFILESIZE) { // sam's suggestion - conoutf("map %s is too large to receive", text); - } else { - if(p.remaining() < size) - { - p.forceoverread(); - break; - } - if(securemapcheck(text)) - { - p.len += size; - break; - } - writemap(path(text), mapsize, &p.buf[p.len]); + if(mapsize <= MAXMAPSENDSIZE && cfgsizegz <= MAXMAPSENDSIZE && size <= MAXMAPSENDSIZE && cfgsize <= MAXCFGFILESIZE && size <= p.remaining() && !securemapcheck(behindpath(text))) + { + writemap(behindpath(text), mapsize, &p.buf[p.len]); p.len += mapsize; - writecfggz(path(text), cfgsize, cfgsizegz, &p.buf[p.len]); + writecfggz(behindpath(text), cfgsize, cfgsizegz, &p.buf[p.len]); p.len += cfgsizegz; } break; diff --git a/source/src/command.cpp b/source/src/command.cpp index fffb24e6..693ca127 100644 --- a/source/src/command.cpp +++ b/source/src/command.cpp @@ -11,6 +11,11 @@ inline bool identaccessdenied(ident *id); void cslimiterr(const char*msg); +#ifdef STANDALONE + #define FLAGMAPCONFIGERR(x) +#else + #define FLAGMAPCONFIGERR(x) flagmapconfigerror(x); +#endif char *exchangestr(char *o, const char *n) { delete[] o; return newstring(n); } @@ -381,7 +386,6 @@ char *parseexp(const char *&p, int right, int rec) // parse any nest screrr: scripterr(); - flagmapconfigerror(LWW_SCRIPTERR * 4); return NULL; } @@ -398,7 +402,6 @@ char *lookup(char *n, int levels) // find value of ide } conoutf("unknown alias lookup: %s", n); scripterr(); - flagmapconfigerror(LWW_SCRIPTERR * 4); return n; } @@ -407,7 +410,7 @@ char *parseword(const char *&p, int arg, int *infix, int rec) p += strspn(p, " \t"); if(p[0]=='/' && p[1]=='/') p += strcspn(p, "\n\r\0"); if(*p=='"') return parsequotes(p); - if(*p=='(' && !currentcontextisolated) return parseexp(p, ')', rec + 1); + if(*p=='(' && !currentcontextisolated) return parseexp(p, ')', rec + 1); // on isolated contexts, only quotes are parsed if(*p=='[' && !currentcontextisolated) return parseexp(p, ']', rec + 1); int lvls = currentcontextisolated ? 0 : strspn(p, "$"); if(lvls && (p[lvls]=='(' || p[lvls]=='[')) @@ -524,7 +527,7 @@ char *executeret(const char *p) // all evaluation happens here, { conoutf("unknown command: %s", c); scripterr(); - flagmapconfigerror(LWW_SCRIPTERR * 4); + FLAGMAPCONFIGERR(LWW_SCRIPTERR * 4); } setretval(newstring(c)); } @@ -532,7 +535,7 @@ char *executeret(const char *p) // all evaluation happens here, { conoutf("not allowed in this execution context: %s", id->name); scripterr(); - flagmapconfigerror(LWW_SCRIPTERR * 4); + FLAGMAPCONFIGERR(LWW_SCRIPTERR * 4); } else { @@ -591,7 +594,7 @@ char *executeret(const char *p) // all evaluation happens here, { i1 = i1minval ? id->minval : id->maxval; // clamp to valid range conoutf("valid range for %s is %d..%d", id->name, id->minval, id->maxval); - flagmapconfigerror(LWW_SCRIPTERR); + FLAGMAPCONFIGERR(LWW_SCRIPTERR); } *id->storage.i = i1; if(id->fun) ((void (__cdecl *)())id->fun)(); // call trigger function if available @@ -608,7 +611,7 @@ char *executeret(const char *p) // all evaluation happens here, { f1 = f1minvalf ? id->minvalf : id->maxvalf; // clamp to valid range conoutf("valid range for %s is %s..%s", id->name, floatstr(id->minvalf), floatstr(id->maxvalf)); - flagmapconfigerror(LWW_SCRIPTERR * 2); + FLAGMAPCONFIGERR(LWW_SCRIPTERR * 2); } *id->storage.f = f1; if(id->fun) ((void (__cdecl *)())id->fun)(); // call trigger function if available @@ -1306,7 +1309,6 @@ void addpunct(char *s, char *type) // Easily inject a string into various CubeSc else result(punct); } COMMAND(addpunct, "ss"); -#endif void toany(char *s, int (*c)(int)) { while(*s) { *s = (*c)(*s); s++; } } @@ -1331,6 +1333,8 @@ void sortlist(char *list) } COMMAND(sortlist, "c"); +#endif + void modifyvar(const char *name, int arg, char op) { ident *id = idents->access(name); @@ -1488,36 +1492,10 @@ void bnot_(int *a) { intret(~(*a)); } COMMANDN(!b, bnot_, "i"); COMMANDF(strcmp, "ss", (char *a, char *b) { intret((strcmp(a, b) == 0) ? 1 : 0); }); -COMMANDF(rnd, "i", (int *a) { intret(*a>0 ? rnd(*a) : 0); }); - #ifndef STANDALONE -const char *escapestring(const char *s, bool force, bool noquotes) -{ - static vector strbuf[3]; - static int stridx = 0; - if(noquotes) force = false; - if(!s) return force ? "\"\"" : ""; - if(!force && !*(s + strcspn(s, "\"/\\;()[] \f\t\n\r$"))) return s; - stridx = (stridx + 1) % 3; - vector &buf = strbuf[stridx]; - buf.setsize(0); - if(!noquotes) buf.add('"'); - for(; *s; s++) switch(*s) - { - case '\n': buf.put("\\n", 2); break; - case '\r': buf.put("\\n", 2); break; - case '\t': buf.put("\\t", 2); break; - case '\a': buf.put("\\a", 2); break; - case '\f': buf.put("\\f", 2); break; - case '"': buf.put("\\\"", 2); break; - case '\\': buf.put("\\\\", 2); break; - default: buf.add(*s); break; - } - if(!noquotes) buf.add('"'); - buf.add(0); - return buf.getbuf(); -} +COMMANDF(rnd, "i", (int *a) { intret(*a>0 ? rnd(*a) : 0); }); + COMMANDF(escape, "s", (const char *s) { result(escapestring(s));}); int sortident(ident **a, ident **b) { return strcasecmp((*a)->name, (*b)->name); } @@ -1703,6 +1681,7 @@ int popscontext() return execcontext; } +#ifndef STANDALONE void scriptcontext(char *context, char *idname) { if(contextsealed) return; @@ -1721,6 +1700,7 @@ void isolatecontext(char *context) COMMAND(isolatecontext, "s"); COMMANDF(sealcontexts, "",() { contextsealed = true; }); +#endif inline bool identaccessdenied(ident *id) // check if ident is allowed in current context { @@ -1737,8 +1717,10 @@ void scripterr() ASSERT(execcontext >= 0 && execcontext < IEXC_NUM); if(curcontext) conoutf("(%s: %s [%s])", curcontext, curinfo, contextnames[execcontext]); else conoutf("(from console or builtin [%s])", contextnames[execcontext]); +#ifndef STANDALONE clientlogf("exec nesting level: %d", executionstack.length()); clientlogf("%s", executionstack.length() ? executionstack.last() : ":::nevermind:::"); +#endif } void setcontext(const char *context, const char *info) @@ -1808,5 +1790,7 @@ void debugline(char *fname, char *line) // print one line to a logfile } } COMMAND(debugline, "ss"); + +COMMANDF(numberofidents, "", () { intret(idents->numelems); }); #endif #endif diff --git a/source/src/console.cpp b/source/src/console.cpp index b2452794..302bc909 100644 --- a/source/src/console.cpp +++ b/source/src/console.cpp @@ -330,7 +330,6 @@ void saycommand(char *init) // turns input to the comman textinput(saycommandon, TI_CONSOLE); keyrepeat(saycommandon, KR_CONSOLE); setscope(false); - setburst(false); copystring(cmdline.buf, init ? escapestring(init, false, true) : ""); DELETEA(cmdaction); DELETEA(cmdprompt); diff --git a/source/src/crypto.cpp b/source/src/crypto.cpp index 53cef444..58587c51 100644 --- a/source/src/crypto.cpp +++ b/source/src/crypto.cpp @@ -327,22 +327,6 @@ int __hashtest() // crash, if hash functions don't work properly - no one ever t static int __ht = __hashtest(); #endif -///////////////////////////////////////////////////////// FNV-1a /////////////////////////////////////////////////////////////////// -// Fowler–Noll–Vo is a non-cryptographic hash function created by Glenn Fowler, Landon Curt Noll, and Phong Vo. It is public domain. -// We use the 32-bit-version here. - -inline void fnv1a_init(uint32_t &hash) -{ - hash = 2166136261UL; -} - -inline void fnv1a_add(uint32_t &hash, uchar byte) -{ - hash ^= byte; - hash *= 16777619; -} - - ////////////////////////// crypto rand (Mersenne twister) //////////////////////////////////////// #define MT_N (624) @@ -369,13 +353,10 @@ void seedMT(uint seed) uint randomMT() { - int cur = mt_next; - if(++mt_next >= MT_N) - { - if(mt_next > MT_N) { seedMT(5489U + time(NULL)); cur = mt_next++; } - else mt_next = 0; - } - uint y = (mt_state[cur] & 0x80000000U) | (mt_state[mt_next] & 0x7FFFFFFFU); + int nxt = (mt_next + 1) % MT_N, cur = (nxt + MT_N -1) % MT_N; + if(mt_next >= MT_N) seedMT(5489U + time(NULL)); + mt_next = nxt; + uint y = (mt_state[cur] & 0x80000000U) | (mt_state[nxt] & 0x7FFFFFFFU); mt_state[cur] = y = mt_state[cur < MT_N - MT_M ? cur + MT_M : cur + MT_M - MT_N] ^ (y >> 1) ^ (-int(y & 1U) & MT_K); y ^= (y >> 11); y ^= (y << 7) & 0x9D2C5680U; @@ -669,12 +650,12 @@ void ed25519test(char *vectorfilename) // check ed25519 functions with test vec path(vectorfilename); stopwatch watch; int vectorfilesize = 0; - char *vectorfile = loadfile(vectorfilename, &vectorfilesize); + char *vectorfile = loadfile(vectorfilename, &vectorfilesize), *b; if(!vectorfile) { conoutf("could not read test vector file %s", vectorfilename); return; } conoutf("loaded %d bytes from %s", vectorfilesize, vectorfilename); watch.start(); int lines = 0, inputerrors = 0, pubfail = 0, signfail = 0, verifyfail = 0, verifyfail2 = 0; - for(char *l = strtok(vectorfile, "\n"); l; l = strtok(NULL, "\n")) + for(char *l = strtok_r(vectorfile, "\n", &b); l; l = strtok_r(NULL, "\n", &b)) { // a line consists of 32 bytes private key, 32 byte public key, ":", 32 byte public key (again), ":", message, ":", signed message, ":" char *vprivpub = l, *vpub = strchr(l, ':'), *vmsg, *vsmsg, hextemp[65]; int linelen = strlen(l), msglen = 0; @@ -745,7 +726,7 @@ VARP(authmemusage, 2, 24, (1<<10) - 1); // megabytes of RAM to use for passw VARP(authrounds, 0, 0, INT_MAX - 1); // create new password hashes with a fixed number of rounds (if authrounds > 0) VARP(authmaxtime, 1<<9, 1<<12, 1<<16); // create new password hashes with a fixed amount of time (in ms) -static uchar *sk = NULL; // game key +uchar *sk = NULL; // game key static struct { uchar *salt, *priv; uint pwdcfg; } passdargs; static int passdeferred(void *pass) // decrypt the private key in the background @@ -885,14 +866,14 @@ void authsetup(char **args, int numargs) // set up private and public keys xor_block(prepriv, keyhash, preprivlen); } const char *fn = numargs > 2 && args[2][0] ? path(args[2]) : AUTHPREPRIVATECFGFILE; - char *oldfile = loadfile(fn, NULL); + char *oldfile = loadfile(fn, NULL), *b; stream *f = openfile(fn, "wb"); if(f) { f->printf("\n" "// remove this file from your computer immediately!\n" "// either print it out and delete it or move it to a thumbdrive.\n" "// YOU DO NOT NEED THIS FILE TO PLAY AC!\n" "\n"); - if(oldfile) for(char *l = strtok(oldfile, "\n\r"); l; l = strtok(NULL, "\n\r")) if(*l) f->printf("// %s\n", l); + if(oldfile) for(char *l = strtok_r(oldfile, "\n\r", &b); l; l = strtok_r(NULL, "\n\r", &b)) if(*l) f->printf("// %s\n", l); f->printf("authsetup pre %s", bin2hex(hextemp, prepriv, preprivlen)); if(preprivpwdcfg) f->printf(" %s %u", bin2hex(hextemp, psalt, 16), preprivpwdcfg & ~1); f->printf("\n\n"); @@ -916,11 +897,11 @@ void authsetup(char **args, int numargs) // set up private and public keys xor_block(priv, keyhash, 32); } const char *fn = numargs > 2 && args[2][0] ? path(args[2]) : AUTHPRIVATECFGFILE; - char *oldfile = loadfile(fn, NULL); + char *oldfile = loadfile(fn, NULL), *b; stream *f = openfile(fn, "wb"); if(f) { - if(oldfile) for(char *l = strtok(oldfile, "\n\r"); l; l = strtok(NULL, "\n\r")) if(*l) f->printf("// %s\n", l); + if(oldfile) for(char *l = strtok_r(oldfile, "\n\r", &b); l; l = strtok_r(NULL, "\n\r", &b)) if(*l) f->printf("// %s\n", l); f->printf("\nauthsetup priv %s", bin2hex(hextemp, priv, 32)); if(privpwdcfg) f->printf(" %s %u", bin2hex(hextemp, salt, 16), privpwdcfg); f->printf("\nauthsetup pub %s\n\n", bin2hex(hextemp, keyhash + 32, 32)); @@ -1008,7 +989,7 @@ void authkey_(char **args, int numargs) // set up misc keys stream *f = openfile(AUTHKEYSCFGFILE, "wb"); if(f) { // don't really delete the old keys, just comment them out - for(char *l = strtok(oldfile, "\n\r"); l; l = strtok(NULL, "\n\r")) if(*l) f->printf("// %s\n", l); + for(char *b, *l = strtok_r(oldfile, "\n\r", &b); l; l = strtok_r(NULL, "\n\r", &b)) if(*l) f->printf("// %s\n", l); delete f; } delete[] oldfile; @@ -1111,7 +1092,7 @@ static int certtypemaxrank[] = { 0, 1, 2, 3, 3, 3, 0 }; // max allowed rank of static int certtypeexpiration[] = { 0, 0, 0, 2 * CERTTIMEMONTH, CERTTIMEMONTH, 3 * CERTTIMEMONTH, CERTTIMEMONTH }; // time after which certs are usually re-signed, after 3x the time they expire vector certs; -hashtable certblacklist; +hashtable certblacklist; static inline int aktcerttime() { return time(NULL) / (time_t) 60; } // use minutes instead of seconds as timebase, so int will be more than enough @@ -1130,7 +1111,7 @@ bool cert::parse() // parse orgmsg[] and check the signature if(hex2bin(signature, workmsg + certheaderlen, 64) == 64) { // parse all lines certline line; - for(char *l = strtok(workmsg + certheaderlenfull, "\n\r"); l; l = strtok(NULL, "\n\r")) + for(char *b, *l = strtok_r(workmsg + certheaderlenfull, "\n\r", &b); l; l = strtok_r(NULL, "\n\r", &b)) { // get comment line.comment = strstr(l, "//"); @@ -1364,7 +1345,7 @@ void rebuildcerttree() // determines the rank of all certs; ages them; rem if(c->days2renew < 0) c->needsrenewal = true; if(c->days2expire < 0) { c->expired = true; c->isvalid = false; } } - if(c->pubkey && certblacklist.access(c->pubkey)) + if(c->pubkey && certblacklist.access(*((uchar32*)c->pubkey))) { c->blacklisted = true; c->isvalid = false; diff --git a/source/src/cube.h b/source/src/cube.h index 956850ac..cdd4154a 100644 --- a/source/src/cube.h +++ b/source/src/cube.h @@ -43,8 +43,7 @@ extern vector ents; // map entities extern vec worldpos, camup, camright, camdir; // current target of the crosshair in the world extern int lastmillis, totalmillis, skipmillis; // last time extern int curtime; // current frame time -extern int interm; -extern int gamemode, nextmode; +extern int gamemode; extern int gamespeed; extern int xtraverts; extern float fovy, aspect; @@ -61,7 +60,7 @@ extern float waterlevel; #define AC_VERSION 1202 #define AC_MASTER_URI "ms.cubers.net" #define AC_MASTER_PORT 28760 -#define MAXCL 16 +#define MAXCLIENTSONMASTER 16 // FIXME #define CONFIGROTATEMAX 5 // keep 5 old versions of saved.cfg and init.cfg around #define DEFAULT_FOG 180 diff --git a/source/src/entities.cpp b/source/src/entities.cpp index 225a866f..81461f62 100644 --- a/source/src/entities.cpp +++ b/source/src/entities.cpp @@ -113,103 +113,94 @@ void renderentarrow(const entity &e, const vec &dir, float radius) glEnable(GL_TEXTURE_2D); } -void renderentities() +void rendereditentities() { - int closest = editmode ? closestent() : -1; - if(editmode && !reflecting && !refracting && !stenciling) - { - static int lastsparkle = 0; - if(lastmillis - lastsparkle >= 20) + if(!editmode) return; + int closest = closestent(); + + static int lastsparkle = 0; + if(lastmillis - lastsparkle >= 20) + { // render edit mode sparklies + lastsparkle = lastmillis - (lastmillis%20); + loopv(ents) { - lastsparkle = lastmillis - (lastmillis%20); - loopv(ents) + entity &e = ents[i]; + if(e.type==NOTUSED) continue; + if(edithideentmask & (1 << (e.type - 1))) continue; + vec v(e.x, e.y, e.z); + if(vec(v).sub(camera1->o).dot(camdir) < 0) continue; + int sc = PART_ECARROT; // use "carrot" for unknown types + if(i == closest) { - entity &e = ents[i]; - if(e.type==NOTUSED) continue; - if(edithideentmask & (1 << (e.type - 1))) continue; - vec v(e.x, e.y, e.z); - if(vec(v).sub(camera1->o).dot(camdir) < 0) continue; - int sc = PART_ECARROT; // use "carrot" for unknown types - if(i == closest) - { - sc = PART_ECLOSEST; // blue - } - else switch(e.type) - { - case LIGHT: sc = PART_ELIGHT; break; // white - case PLAYERSTART: sc = PART_ESPAWN; break; // green - case I_CLIPS: - case I_AMMO: - case I_GRENADE: sc = PART_EAMMO; break; // red - case I_HEALTH: - case I_HELMET: - case I_ARMOUR: - case I_AKIMBO: sc = PART_EPICKUP; break; // yellow - case MAPMODEL: - case SOUND: sc = PART_EMODEL; break; // magenta - case LADDER: - case CLIP: - case PLCLIP: sc = PART_ELADDER; break; // grey - case CTF_FLAG: sc = PART_EFLAG; break; // turquoise - default: break; - } - particle_splash(sc, i == closest ? 14 : 2, i == closest ? 50 : 40, v); + sc = PART_ECLOSEST; // blue } + else switch(e.type) + { + case LIGHT: sc = PART_ELIGHT; break; // white + case PLAYERSTART: sc = PART_ESPAWN; break; // green + case I_CLIPS: + case I_AMMO: + case I_GRENADE: sc = PART_EAMMO; break; // red + case I_HEALTH: + case I_HELMET: + case I_ARMOUR: + case I_AKIMBO: sc = PART_EPICKUP; break; // yellow + case MAPMODEL: + case SOUND: sc = PART_EMODEL; break; // magenta + case LADDER: + case CLIP: + case PLCLIP: sc = PART_ELADDER; break; // grey + case CTF_FLAG: sc = PART_EFLAG; break; // turquoise + default: break; + } + particle_splash(sc, i == closest ? 14 : 2, i == closest ? 50 : 40, v); } } + loopv(ents) { entity &e = ents[i]; - if(isitem(e.type)) - { - if((!OUTBORD(e.x, e.y) && e.spawned) || editmode) - { - renderent(e); - } - } - else if(editmode) - { - if(e.type==CTF_FLAG) + if(OUTBORD(e.x, e.y)) continue; + if(isitem(e.type)) renderent(e); + else switch(e.type) + { // render special representation for entities in edit mode + case CTF_FLAG: { defformatstring(path)("pickups/flags/%s", team_basestring(e.attr2)); rendermodel(path, ANIM_FLAG|ANIM_LOOP, 0, 0, vec(e.x, e.y, (float)S(e.x, e.y)->floor), 0, float(e.attr1) / ENTSCALE10, 0, 120.0f); + break; } - else if((e.type == CLIP || e.type == PLCLIP) && showclips && !stenciling) - { - renderclip(e.type, e.x, e.y, float(e.attr2) / ENTSCALE5, float(e.attr3) / ENTSCALE5, float(e.attr4) / ENTSCALE5, float(e.attr1) / ENTSCALE10, float(e.attr6) / (4 * ENTSCALE10), e.attr7); - } - else if(e.type == MAPMODEL && showclips && showmodelclipping && !stenciling) - { - mapmodelinfo *mmi = getmminfo(e.attr2); - if(mmi && mmi->h) + case CLIP: + case PLCLIP: + if(showclips) renderclip(e.type, e.x, e.y, float(e.attr2) / ENTSCALE5, float(e.attr3) / ENTSCALE5, float(e.attr4) / ENTSCALE5, float(e.attr1) / ENTSCALE10, float(e.attr6) / (4 * ENTSCALE10), e.attr7); + break; + + case MAPMODEL: + if(showclips && showmodelclipping) { - renderclip(e.type, e.x, e.y, mmi->rad, mmi->rad, mmi->h, mmi->zoff + float(e.attr3) / ENTSCALE5, 0, 0); + mapmodelinfo *mmi = getmminfo(e.attr2); + if(mmi && mmi->h) renderclip(e.type, e.x, e.y, mmi->rad, mmi->rad, mmi->h, mmi->zoff + float(e.attr3) / ENTSCALE5, 0, 0); } - } - else if(e.type == LADDER && showladderentities && !stenciling) - { - renderclip(e.type, e.x, e.y, 0, 0, e.attr1, 0, 0, 3); - } - else if(e.type == PLAYERSTART && showplayerstarts) - { - vec o(e.x, e.y, 0); - if(!OUTBORD(e.x, e.y)) o.z += S(e.x, e.y)->floor; - const char *skin; - switch(e.attr2) + break; + + case LADDER: + if(showladderentities) renderclip(e.type, e.x, e.y, 0, 0, e.attr1, 0, 0, 3); + break; + + case PLAYERSTART: + if(showplayerstarts) { - case 0: skin = "packages/models/playermodels/CLA/red.jpg"; break; - case 1: skin = "packages/models/playermodels/RVSF/blue.jpg"; break; - case 100: skin = "packages/models/playermodels/ffaspawn.jpg"; break; - default: skin = "packages/models/playermodels/unknownspawn.jpg"; break; + vec o(e.x, e.y, S(e.x, e.y)->floor); + const char *skin = "packages/models/playermodels/unknownspawn.jpg"; + switch(e.attr2) + { + case 0: skin = "packages/models/playermodels/CLA/red.jpg"; break; + case 1: skin = "packages/models/playermodels/RVSF/blue.jpg"; break; + case 100: skin = "packages/models/playermodels/ffaspawn.jpg"; break; + } + rendermodel("playermodels", ANIM_IDLE, -(int)textureload(skin)->id, 1.5f, o, 0, e.attr1 / ENTSCALE10 + 90, 0); } - rendermodel("playermodels", ANIM_IDLE, -(int)textureload(skin)->id, 1.5f, o, 0, e.attr1 / ENTSCALE10 + 90, 0); - } - } - if(editmode && i == closest && !stenciling) - { - switch(e.type) - { - case PLAYERSTART: + if(i == closest) { glColor3f(0, 1, 1); vec dir(0, -1, 0); @@ -217,11 +208,26 @@ void renderentities() renderentarrow(e, dir, 4); glColor3f(1, 1, 1); } - default: break; + break; + } + } +} + +void renderentities() +{ + if(editmode) return; + loopv(ents) + { + entity &e = ents[i]; + if(isitem(e.type)) + { + if(e.spawned && !OUTBORD(e.x, e.y)) + { + renderent(e); } } } - if(m_flags && !editmode) loopi(2) + if(m_flags) loopi(2) { flaginfo &f = flaginfos[i]; switch(f.state) @@ -234,6 +240,7 @@ void renderentities() rendermodel(path, ANIM_FLAG|ANIM_START|ANIM_DYNALLOC, 0, 0, vec(f.actor->o).add(vec(0, 0, 0.3f+(sinf(lastmillis/100.0f)+1)/10)), 0, lastmillis/2.5f, 0, 120.0f); } break; + case CTFF_INBASE: if(!clentstats.flags[i]) break; case CTFF_DROPPED: @@ -623,6 +630,7 @@ void entstats_(void) } if(es.entcnt[CTF_FLAG]) conoutf(" flag distance: %d", es.flagentdistance); conoutf(" map capabilities: has ffa spawns %d, has team spawns %d, has flags %d", es.hasffaspawns ? 1 : 0, es.hasteamspawns ? 1 : 0, es.hasflags ? 1 : 0); + if(es.modes_possible & GMMASK__MPNOCOOP) conoutf(" possible multiplayer modes: %s", gmode_enum(es.modes_possible & GMMASK__MPNOCOOP, txt)); conoutf("total entities: %d", ents.length()); intret(xmodels + xsounds); } diff --git a/source/src/entity.h b/source/src/entity.h index 5172c609..ea9e4b92 100644 --- a/source/src/entity.h +++ b/source/src/entity.h @@ -53,7 +53,7 @@ struct entity : persistent_entity } }; -enum { GUN_KNIFE = 0, GUN_PISTOL, GUN_CARBINE, GUN_SHOTGUN, GUN_SUBGUN, GUN_SNIPER, GUN_ASSAULT, GUN_CPISTOL, GUN_GRENADE, GUN_AKIMBO, NUMGUNS }; +enum { GUN_KNIFE = 0, GUN_PISTOL, GUN_CARBINE, GUN_SHOTGUN, GUN_SUBGUN, GUN_SNIPER, GUN_ASSAULT, GUN_GRENADE, GUN_AKIMBO, NUMGUNS }; #define valid_weapon(g) ((g) >= GUN_KNIFE && (g) < NUMGUNS) #define reloadable_gun(g) ((g) != GUN_KNIFE && (g) != GUN_GRENADE) @@ -113,19 +113,14 @@ static inline const char *team_string(int t, bool abbr = false) { const char **n enum { ENT_PLAYER = 0, ENT_BOT, ENT_CAMERA, ENT_BOUNCE }; enum { CS_ALIVE = 0, CS_DEAD, CS_SPAWNING, CS_LAGGED, CS_EDITING, CS_SPECTATE }; -enum { CR_DEFAULT = 0, CR_ADMIN }; +enum { CR_DEFAULT = 0, CR_MASTER, CR_ADMIN, CR_OWNER, CR_NUM }; enum { SM_NONE = 0, SM_DEATHCAM, SM_FOLLOW1ST, SM_FOLLOW3RD, SM_FOLLOW3RD_TRANSPARENT, SM_FLY, SM_OVERVIEW, SM_NUM }; +enum { FPCN_VOID = -4, FPCN_DEATHCAM = -2, FPCN_FLY = -2, FPCN_OVERVIEW = -1 }; -class worldobject +class physent { public: - virtual ~worldobject() {}; -}; - -class physent : public worldobject -{ -public: - vec o, vel, vel_t; // origin, velocity + vec o, vel; // origin, velocity vec deltapos, newpos; // movement interpolation float yaw, pitch, roll; // used as vec in one place float pitchvel; @@ -133,7 +128,7 @@ public: int timeinair; // used for fake gravity float radius, eyeheight, maxeyeheight, aboveeye; // bounding box size bool inwater; - bool onfloor, onladder, jumpnext, crouching, crouchedinair, trycrouch, cancollide, stuck, scoping, shoot; + bool onfloor, onladder, jumpnext, jumpd, crouching, crouchedinair, trycrouch, cancollide, stuck, scoping; int lastjump; float lastjumpheight; int lastsplash; @@ -143,7 +138,7 @@ public: int last_pos; physent() : o(0, 0, 0), deltapos(0, 0, 0), newpos(0, 0, 0), yaw(270), pitch(0), roll(0), pitchvel(0), - crouching(false), crouchedinair(false), trycrouch(false), cancollide(true), stuck(false), scoping(false), shoot(false), lastjump(0), lastjumpheight(200), lastsplash(0), state(CS_ALIVE), last_pos(0) + crouching(false), crouchedinair(false), trycrouch(false), cancollide(true), stuck(false), scoping(false), lastjump(0), lastjumpheight(200), lastsplash(0), state(CS_ALIVE), last_pos(0) { reset(); } @@ -158,10 +153,10 @@ public: void reset() { - vel.x = vel.y = vel.z = eyeheightvel = vel_t.x = vel_t.y = vel_t.z = 0.0f; + vel.x = vel.y = vel.z = eyeheightvel = 0.0f; move = strafe = 0; timeinair = lastjump = lastsplash = 0; - onfloor = onladder = inwater = jumpnext = crouching = crouchedinair = trycrouch = stuck = false; + onfloor = onladder = inwater = jumpnext = jumpd = crouching = crouchedinair = trycrouch = stuck = false; last_pos = 0; } @@ -433,6 +428,7 @@ public: int spectatemode, followplayercn; int eardamagemillis; float maxroll, maxrolleffect, movroll, effroll; // roll added by movement and damage + int ffov, scopefov; bool allowmove() { return state!=CS_DEAD || spectatemode==SM_FLY; } weapon *weapons[NUMGUNS]; @@ -451,7 +447,7 @@ public: bool nocorpse; playerent() : curskin(0), clientnum(-1), lastupdate(0), plag(0), ping(0), address(0), lifesequence(0), frags(0), flagscore(0), deaths(0), points(0), tks(0), lastpain(0), lastvoicecom(0), lastdeath(0), clientrole(CR_DEFAULT), - team(TEAM_SPECT), spectatemode(SM_NONE), eardamagemillis(0), maxroll(ROLLMOVDEF), maxrolleffect(ROLLEFFDEF), movroll(0), effroll(0), + team(TEAM_SPECT), spectatemode(SM_NONE), followplayercn(FPCN_VOID), eardamagemillis(0), maxroll(ROLLMOVDEF), maxrolleffect(ROLLEFFDEF), movroll(0), effroll(0), ffov(0), scopefov(0), prevweaponsel(NULL), weaponsel(NULL), nextweaponsel(NULL), primweap(NULL), nextprimweap(NULL), lastattackweapon(NULL), smoothmillis(-1), head(-1, -1, -1), ignored(false), muted(false), nocorpse(false) @@ -502,6 +498,7 @@ public: void resetspec() { spectatemode = SM_NONE; + followplayercn = FPCN_VOID; } void respawn() @@ -551,6 +548,10 @@ public: t = team_base(t < 0 ? team : t); nextskin[t] = iabs(s) % maxskin[t]; } + void startmap() + { + frags = flagscore = deaths = lifesequence = points = tks = 0; + } }; @@ -647,6 +648,3 @@ public: void onmoved(const vec &dist); }; -enum {MD_FRAGS = 0, MD_DEATHS, END_MDS}; -struct medalsst {bool assigned; int cn; int item;}; - diff --git a/source/src/hudgun.h b/source/src/hudgun.h index 18fabcfc..5414719c 100644 --- a/source/src/hudgun.h +++ b/source/src/hudgun.h @@ -115,7 +115,6 @@ void preload_hudguns() { loopi(NUMGUNS) { - if(i == GUN_CPISTOL) continue; defformatstring(path)("weapons/%s", guns[i].modelname); loadmodel(path); } diff --git a/source/src/log.cpp b/source/src/log.cpp index 47b2fc9b..2351508c 100644 --- a/source/src/log.cpp +++ b/source/src/log.cpp @@ -32,8 +32,15 @@ static int facility = -1, consolethreshold = ACLOG_INFO; static bool timestamp = false, enabled = false; -bool initlogging(const char *identity, int facility_, int consolethres, int filethres, int syslogthres, bool logtimestamp) +enum { LOGTARGET_CONSOLE = 1, LOGTARGET_FILE = 2, LOGTARGET_SYSLOG = 4 }; + +static char loglevelenabled[ACLOG_NUM]; + +bool initlogging(const char *identity, int facility_, int consolethres, int filethres, int syslogthres, bool logtimestamp, const char *logfilepath) { + static void *logworkerthread_id = NULL; + extern int logworkerthread(void *nop); + if(!logworkerthread_id) logworkerthread_id = sl_createthread(logworkerthread, NULL); // always start, if dedicated server facility = facility_; timestamp = logtimestamp; if(consolethres >= 0) consolethreshold = min(consolethres, (int)ACLOG_NUM); @@ -49,7 +56,8 @@ bool initlogging(const char *identity, int facility_, int consolethres, int file if((logsock = enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM)) == ENET_SOCKET_NULL || enet_address_set_host(&logdest, "localhost") < 0) syslogthreshold = ACLOG_NUM; #endif } - formatstring(filepath)("serverlog_%s_%s.txt", timestring(true), identity); + formatstring(filepath)("%sserverlog_%s_%s.txt", logfilepath, timestring(true), identity); + path(filepath); if(fp) { fclose(fp); fp = NULL; } if(filethreshold < ACLOG_NUM) { @@ -62,52 +70,155 @@ bool initlogging(const char *identity, int facility_, int consolethres, int file if(syslogthreshold < ACLOG_NUM) concatformatstring(msg, ", \"%s\", local%d", ident, facility); concatformatstring(msg, "), timestamp(%s)", timestamp ? "ENABLED" : "DISABLED"); enabled = consolethreshold < ACLOG_NUM || fp || syslogthreshold < ACLOG_NUM; + loopi(ACLOG_NUM) loglevelenabled[i] = (consolethreshold <= i ? LOGTARGET_CONSOLE : 0) | (fp && filethreshold <= i ? LOGTARGET_FILE : 0) | (syslogthreshold <= i ? LOGTARGET_SYSLOG : 0); if(enabled) printf("%s\n", msg); return enabled; } void exitlogging() { + loopi(ACLOG_NUM) loglevelenabled[i] = 0; if(fp) { fclose(fp); fp = NULL; } #ifdef AC_USE_SYSLOG - if(syslogthreshold < ACLOG_NUM) closelog(); + if(enabled && syslogthreshold < ACLOG_NUM) closelog(); #endif syslogthreshold = ACLOG_NUM; enabled = false; } -bool logline(int level, const char *msg, ...) +bool logcheck(int level) { - if(!enabled) return false; - if(level < 0 || level >= ACLOG_NUM) return false; - defvformatstring(sf, msg, msg); - filtertext(sf, sf, FTXT__LOG); - bool logtocon = consolethreshold <= level, logtofile = fp && filethreshold <= level, logtosyslog = syslogthreshold <= level; - const char *ts = timestamp ? timestring(true, "%b %d %H:%M:%S ") : "", *ld = levelprefix[level]; - char *p, *l = sf; - do - { // break into single lines first - if((p = strchr(l, '\n'))) *p = '\0'; - if(logtocon) printf("%s%s%s\n", ts, ld, l); - if(logtofile) fprintf(fp, "%s%s%s\n", ts, ld, l); - if(logtosyslog) + ASSERT(level >= 0 && level < ACLOG_NUM); + return loglevelenabled[level] != 0; +} + +struct logline_s { int level; time_t t; string msg; }; + +static ringbuf mainlog; // increase this number, if log overflows occur +static ringbuf threadlog; +static int mainlogoverflow = 0; +static sl_semaphore threadlogsem(1, NULL), threadlogfullsem(0, NULL), mainlogsem(0, NULL); + +extern int stat_mainlog_peaklevel; + +int logworkerthread(void *nop) +{ + string tmp; + for(;;) + { + while(mainlog.empty()) mainlogsem.wait(); + int mainloglevel = (100 * mainlog.length()) / mainlog.maxsize(); + if(mainloglevel > stat_mainlog_peaklevel) stat_mainlog_peaklevel = mainloglevel; + logline_s *ll = mainlog.remove(); + int targets = loglevelenabled[ll->level]; + bool logtocon = (targets & LOGTARGET_CONSOLE) != 0, logtofile = fp && (targets & LOGTARGET_FILE) != 0, logtosyslog = (targets & LOGTARGET_SYSLOG) != 0; + const char *ts = timestamp ? timestring(ll->t, true, "%b %d %H:%M:%S ", tmp) : "", *ld = levelprefix[ll->level]; + char *p, *l = ll->msg; + do + { // break into single lines first + if((p = strchr(l, '\n'))) *p = '\0'; + if(logtocon) printf("%s%s%s\n", ts, ld, l); + if(logtofile) fprintf(fp, "%s%s%s\n", ts, ld, l); + if(logtosyslog) #ifdef AC_USE_SYSLOG - syslog(levels[level], "%s", l); + syslog(levels[ll->level], "%s", l); #else - { - defformatstring(text)("<%d>%s: %s", (16 + facility) * 8 + levels[level], ident, l); // no TIMESTAMP, no hostname: syslog will add this - ENetBuffer buf; - buf.data = text; - buf.dataLength = strlen(text); - enet_socket_send(logsock, &logdest, &buf, 1); + { + defformatstring(text)("<%d>%s: %s", (16 + facility) * 8 + levels[ll->level], ident, l); // no TIMESTAMP, no hostname: syslog will add this + ENetBuffer buf; + buf.data = text; + buf.dataLength = strlen(text); + enet_socket_send(logsock, &logdest, &buf, 1); + } +#endif + l = p + 1; } + while(p); +#ifdef _DEBUG + if(logtocon) fflush(stdout); + if(logtofile) fflush(fp); #endif - l = p + 1; + delete ll; + } + return 0; +} + +void mlog(int level, const char *msg, ...) // log line from main thread (never blocks) +{ + ASSERT(level >= 0 && level < ACLOG_NUM && ismainthread()); + if(!loglevelenabled[level]) return; + if(mainlog.full()) { mainlogoverflow++; return; } // do. not. block. + logline_s *ll = mainlog.stage(new logline_s); + vformatstring(ll->msg, msg, msg); + filtertext(ll->msg, ll->msg, FTXT__LOG); + ll->t = time(NULL); + ll->level = level; + mainlog.commit(); + if(mainlogsem.getvalue() < 1) mainlogsem.post(); +} + +void tlog(int level, const char *msg, ...) // log line from other-than-main thread (may block) +{ + ASSERT(level >= 0 && level < ACLOG_NUM && !ismainthread()); + if(!loglevelenabled[level]) return; + threadlogsem.wait(); // properly lock, so different threads can use this + while(threadlog.full()) threadlogfullsem.wait(); // wait for buffer to clear + logline_s *ll = threadlog.stage(new logline_s); + vformatstring(ll->msg, msg, msg); + filtertext(ll->msg, ll->msg, FTXT__LOG); + ll->t = time(NULL); + ll->level = level; + threadlog.commit(); + threadlogsem.post(); +} + +void xlog(int level, const char *msg, ...) // log line from any thread (more expensive: only use for functions, that are called from main and other threads) +{ + ASSERT(level >= 0 && level < ACLOG_NUM); + if(!loglevelenabled[level]) return; + bool ismain = ismainthread(); + if(ismain) + { + if(mainlog.full()) { mainlogoverflow++; return; } // do. not. block. + } + else + { + threadlogsem.wait(); // properly lock, so different threads can use this + while(threadlog.full()) threadlogfullsem.wait(); // wait for buffer to clear + } + logline_s *ll = ismain ? mainlog.stage(new logline_s) : threadlog.stage(new logline_s); + vformatstring(ll->msg, msg, msg); + filtertext(ll->msg, ll->msg, FTXT__LOG); + ll->t = time(NULL); + ll->level = level; + if(ismain) + { + mainlog.commit(); + if(mainlogsem.getvalue() < 1) mainlogsem.post(); } - while(p); -#ifdef _DEBUG - if(logtocon) fflush(stdout); - if(logtofile) fflush(fp); -#endif - return logtocon; + else + { + threadlog.commit(); + threadlogsem.post(); + } +} + +extern int stat_theadlog_peaklevel; + +void poll_logbuffers() // just copy threadlog to mainlog and restart blocked threads, if necessary +{ + int threadloglevel = (100 * threadlog.length()) / threadlog.maxsize(); + if(threadloglevel > stat_theadlog_peaklevel) stat_theadlog_peaklevel = threadloglevel; + while(!mainlog.full() && !threadlog.empty()) + { + mainlog.stage(threadlog.remove()); + mainlog.commit(); + } + if(mainlogoverflow && !mainlog.full()) + { + mlog(ACLOG_ERROR, "main log ringbuffer overflow, %d entries lost", mainlogoverflow); + mainlogoverflow = 0; + } + if(!threadlog.full() && threadlogfullsem.getvalue() < 1) threadlogfullsem.post(); + if(!mainlog.empty() && mainlogsem.getvalue() < 1) mainlogsem.post(); } diff --git a/source/src/main.cpp b/source/src/main.cpp index 4e94c947..44e663f1 100644 --- a/source/src/main.cpp +++ b/source/src/main.cpp @@ -1131,6 +1131,7 @@ int main(int argc, char **argv) char *initscript = NULL; char *initdemo = NULL; + setlocale(LC_ALL, "POSIX"); if(bootclientlog) cvecprintf(*bootclientlog, "######## start logging: %s\n", timestring(true)); const char *initmap = rndmapname(); @@ -1253,8 +1254,8 @@ int main(int argc, char **argv) if(enet_initialize()<0) fatal("Unable to initialise network module"); if (!dedicated) initclient(); - //FIXME the server executed in this way does not catch the SIGTERM or ^C - initserver(dedicated,argc,argv); // never returns if dedicated + + initserver(dedicated); // never returns if dedicated initlog("world (" STRINGIFY(AC_VERSION) ")"); empty_world(7, true); diff --git a/source/src/master.cpp b/source/src/master.cpp index cc35ceb6..d0a34dfe 100644 --- a/source/src/master.cpp +++ b/source/src/master.cpp @@ -30,13 +30,13 @@ void adduser(char *name, char *pubkey) name = newstring(name); userinfo &u = users[name]; u.name = name; - u.pubkey = parsepubkey(pubkey); +// u.pubkey = parsepubkey(pubkey); } COMMAND(adduser, "ss"); void clearusers() { - enumerate(users, userinfo, u, { delete[] u.name; freepubkey(u.pubkey); }); +// enumerate(users, userinfo, u, { delete[] u.name; freepubkey(u.pubkey); }); users.clear(); } COMMAND(clearusers, ""); @@ -149,7 +149,7 @@ struct messagebuf vector gameserverlists, gbanlists; bool updateserverlist = true; -struct client +struct mclient { ENetAddress address; ENetSocket socket; @@ -164,9 +164,9 @@ struct client bool shouldpurge; bool registeredserver; - client() : message(NULL), inputpos(0), outputpos(0), servport(-1), lastauth(0), shouldpurge(false), registeredserver(false) {} + mclient() : message(NULL), inputpos(0), outputpos(0), servport(-1), lastauth(0), shouldpurge(false), registeredserver(false) {} }; -vector clients; +vector mclients; ENetSocket serversocket = ENET_SOCKET_NULL; @@ -199,27 +199,22 @@ void conoutf(const char *fmt, ...) void purgeclient(int n) { - client &c = *clients[n]; + mclient &c = *mclients[n]; if(c.message) c.message->purge(); enet_socket_destroy(c.socket); - delete clients[n]; - clients.remove(n); + delete mclients[n]; + mclients.remove(n); } -void output(client &c, const char *msg, int len = 0) +void output(mclient &c, const char *msg, int len = 0) { if(!len) len = strlen(msg); c.output.put(msg, len); } -void outputf(client &c, const char *fmt, ...) +void outputf(mclient &c, const char *fmt, ...) { - string msg; - va_list args; - va_start(args, fmt); - vformatstring(msg, fmt, args); - va_end(args); - + defvformatstring(msg, fmt, fmt); output(c, msg); } @@ -308,9 +303,9 @@ void gengbanlist() if(m->refs > 0 && !m->endswith(*l)) m->concat(*l); } gbanlists.add(l); - loopv(clients) + loopv(mclients) { - client &c = *clients[i]; + mclient &c = *mclients[i]; if(c.servport >= 0 && !c.message) { c.message = l; @@ -319,7 +314,7 @@ void gengbanlist() } } -void addgameserver(client &c) +void addgameserver(mclient &c) { if(gameservers.length() >= SERVER_LIMIT) return; loopv(gameservers) @@ -347,11 +342,11 @@ void addgameserver(client &c) s.lastping = s.lastpong = 0; } -client *findclient(gameserver &s) +mclient *findclient(gameserver &s) { - loopv(clients) + loopv(mclients) { - client &c = *clients[i]; + mclient &c = *mclients[i]; if(s.address.host == c.address.host && s.port == c.servport) return &c; } @@ -360,7 +355,7 @@ client *findclient(gameserver &s) void servermessage(gameserver &s, const char *msg) { - client *c = findclient(s); + mclient *c = findclient(s); if(c) outputf(*c, msg); } @@ -382,7 +377,7 @@ void checkserverpongs() { if(s.lastping && (!s.lastpong || ENET_TIME_GREATER(s.lastping, s.lastpong))) { - client *c = findclient(s); + mclient *c = findclient(s); if(c) { c->registeredserver = true; @@ -457,7 +452,7 @@ void messagebuf::purge() } // for AUTH: -void purgeauths(client &c) +void purgeauths(mclient &c) { int expired = 0; loopv(c.authreqs) @@ -465,7 +460,7 @@ void purgeauths(client &c) if(ENET_TIME_DIFFERENCE(servtime, c.authreqs[i].reqtime) >= AUTH_TIME) { outputf(c, "failauth %u\n", c.authreqs[i].id); - freechallenge(c.authreqs[i].answer); +// freechallenge(c.authreqs[i].answer); expired = i + 1; } else break; @@ -473,7 +468,7 @@ void purgeauths(client &c) if(expired > 0) c.authreqs.remove(0, expired); } -void reqauth(client &c, uint id, char *name) +void reqauth(mclient &c, uint id, char *name) { if(ENET_TIME_DIFFERENCE(servtime, c.lastauth) < AUTH_THROTTLE) return; @@ -503,22 +498,22 @@ void reqauth(client &c, uint id, char *name) if(c.authreqs.length() >= AUTH_LIMIT) { outputf(c, "failauth %u\n", c.authreqs[0].id); - freechallenge(c.authreqs[0].answer); +// freechallenge(c.authreqs[0].answer); c.authreqs.remove(0); } authreq &a = c.authreqs.add(); a.reqtime = servtime; a.id = id; - uint seed[3] = { (uint)starttime, (uint)servtime, randomMT() }; +// uint seed[3] = { (uint)starttime, (uint)servtime, randomMT() }; static vector buf; buf.setsize(0); - a.answer = genchallenge(u->pubkey, seed, sizeof(seed), buf); +// a.answer = genchallenge(u->pubkey, seed, sizeof(seed), buf); outputf(c, "chalauth %u %s\n", id, buf.getbuf()); } -void confauth(client &c, uint id, const char *val) +void confauth(mclient &c, uint id, const char *val) { purgeauths(c); @@ -526,7 +521,7 @@ void confauth(client &c, uint id, const char *val) { string ip; if(enet_address_get_host_ip(&c.address, ip, sizeof(ip)) < 0) copystring(ip, "-"); - if(checkchallenge(val, c.authreqs[i].answer)) + if(0)//checkchallenge(val, c.authreqs[i].answer)) { outputf(c, "succauth %u\n", id); conoutf("succeeded %u from %s", id, ip); @@ -536,7 +531,7 @@ void confauth(client &c, uint id, const char *val) outputf(c, "failauth %u\n", id); conoutf("failed %u from %s", id, ip); } - freechallenge(c.authreqs[i].answer); +// freechallenge(c.authreqs[i].answer); c.authreqs.remove(i--); return; } @@ -544,7 +539,7 @@ void confauth(client &c, uint id, const char *val) } // :for AUTH -bool checkclientinput(client &c) +bool checkclientinput(mclient &c) { if(c.inputpos<0) return true; char *end = (char *)memchr(c.input, '\n', c.inputpos); @@ -606,9 +601,9 @@ void checkclients() ENET_SOCKETSET_EMPTY(writeset); ENET_SOCKETSET_ADD(readset, serversocket); ENET_SOCKETSET_ADD(readset, pingsocket); - loopv(clients) + loopv(mclients) { - client &c = *clients[i]; + mclient &c = *mclients[i]; if(c.message || c.output.length()) ENET_SOCKETSET_ADD(writeset, c.socket); else ENET_SOCKETSET_ADD(readset, c.socket); maxsock = max(maxsock, c.socket); @@ -620,29 +615,29 @@ void checkclients() { ENetAddress address; ENetSocket clientsocket = enet_socket_accept(serversocket, &address); - if(clients.length()>=CLIENT_LIMIT || checkban(bans, address.host)) enet_socket_destroy(clientsocket); + if(mclients.length()>=CLIENT_LIMIT || checkban(bans, address.host)) enet_socket_destroy(clientsocket); else if(clientsocket!=ENET_SOCKET_NULL) { int dups = 0, oldest = -1; - loopv(clients) if(clients[i]->address.host == address.host) + loopv(mclients) if(mclients[i]->address.host == address.host) { dups++; - if(oldest<0 || clients[i]->connecttime < clients[oldest]->connecttime) oldest = i; + if(oldest<0 || mclients[i]->connecttime < mclients[oldest]->connecttime) oldest = i; } if(dups >= DUP_LIMIT) purgeclient(oldest); - client *c = new client; + mclient *c = new mclient; c->address = address; c->socket = clientsocket; c->connecttime = servtime; c->lastinput = servtime; - clients.add(c); + mclients.add(c); } } - loopv(clients) + loopv(mclients) { - client &c = *clients[i]; + mclient &c = *mclients[i]; if((c.message || c.output.length()) && ENET_SOCKETSET_CHECK(writeset, c.socket)) { const char *data = c.output.length() ? c.output.getbuf() : c.message->getbuf(); @@ -693,7 +688,7 @@ void checkclients() void banclients() { - loopvrev(clients) if(checkban(bans, clients[i]->address.host)) purgeclient(i); + loopvrev(mclients) if(checkban(bans, mclients[i]->address.host)) purgeclient(i); } volatile bool reloadcfg = true; diff --git a/source/src/physics.cpp b/source/src/physics.cpp index 7b74db80..0d03c693 100644 --- a/source/src/physics.cpp +++ b/source/src/physics.cpp @@ -354,7 +354,6 @@ bool collide(physent *d, bool spawn, float drop, float rise) VARFP(maxroll, 0, ROLLMOVDEF, ROLLMOVMAX, player1->maxroll = maxroll); VARFP(maxrolleffect, 0, ROLLEFFDEF, ROLLEFFMAX, player1->maxrolleffect = maxrolleffect); VARP(maxrollremote, 0, ROLLMOVDEF + ROLLEFFDEF, ROLLMOVMAX + ROLLEFFMAX); -//VAR(recoilbackfade, 0, 100, 1000); void resizephysent(physent *pl, int moveres, int curtime, float min, float max) { @@ -517,6 +516,7 @@ void moveplayer(physent *pl, int moveres, bool local, int curtime) { if(pl->jumpnext) { + pl->jumpd = true; pl->jumpnext = false; bool doublejump = pl->lastjump && lastmillis - pl->lastjump < 250 && pl->strafe != 0 && pl->o.z - pl->eyeheight - pl->lastjumpheight > 0.2f; pl->lastjumpheight = pl->o.z - pl->eyeheight; @@ -734,12 +734,7 @@ void moveplayer(physent *pl, int moveres, bool local, int curtime) pl->pitch += pl->pitchvel*(curtime/1000.0f)*pl->maxspeed*(pl->crouching ? 0.75f : 1.0f); pl->pitchvel *= fric-3; pl->pitchvel /= fric; - /*extern int recoiltest; - if(recoiltest) - { - if(pl->pitchvel < 0.05f && pl->pitchvel > 0.001f) pl->pitchvel -= recoilbackfade/100.0f; // slide back - } - else*/ if(pl->pitchvel < 0.05f && pl->pitchvel > 0.001f) pl->pitchvel -= ((playerent *)pl)->weaponsel->info.recoilbackfade/100.0f; // slide back + if(pl->pitchvel < 0.05f && pl->pitchvel > 0.001f) pl->pitchvel -= ((playerent *)pl)->weaponsel->info.recoilbackfade/100.0f; // slide back if(pl->pitchvel) fixcamerarange(pl); // fix pitch if necessary } @@ -899,7 +894,6 @@ COMMAND(crouch, "d"); void fixcamerarange(physent *cam) { - const float MAXPITCH = 90.0f; if(cam->pitch>MAXPITCH) cam->pitch = MAXPITCH; if(cam->pitch<-MAXPITCH) cam->pitch = -MAXPITCH; while(cam->yaw<0.0f) cam->yaw += 360.0f; diff --git a/source/src/platform.h b/source/src/platform.h index 0d90c621..29031938 100644 --- a/source/src/platform.h +++ b/source/src/platform.h @@ -12,6 +12,7 @@ #include #include #include +#include #include #ifdef __GNUC__ #include diff --git a/source/src/protocol.cpp b/source/src/protocol.cpp index 002bac13..a919f1ca 100644 --- a/source/src/protocol.cpp +++ b/source/src/protocol.cpp @@ -3,9 +3,9 @@ #include "cube.h" #ifdef _DEBUG -bool protocoldbg = false; +bool protocoldbg = false, debugmute = false; void protocoldebug(bool enable) { protocoldbg = enable; } -#define DEBUGCOND (protocoldbg) +#define DEBUGCOND (protocoldbg && !debugmute) #endif // all network traffic is in 32bit ints, which are then compressed using the following simple scheme (assumes that most values are small). @@ -123,9 +123,11 @@ float getfloat(ucharbuf &p) template static inline void sendstring_(const char *text, T &p) { + DEBUGCODE(debugmute = true); const char *t = text; if(t) { while(*t) putint(p, *t++); } putint(p, 0); + DEBUGCODE(debugmute = false); DEBUGVAR(text); } void sendstring(const char *t, ucharbuf &p) { sendstring_(t, p); } @@ -134,6 +136,7 @@ void sendstring(const char *t, vector &p) { sendstring_(t, p); } void getstring(char *text, ucharbuf &p, int len) { + DEBUGCODE(debugmute = true); char *t = text; do { @@ -142,9 +145,47 @@ void getstring(char *text, ucharbuf &p, int len) *t = getint(p); } while(*t++); + DEBUGCODE(debugmute = false); DEBUGVAR(text); } +template +static inline void putip4_(T &p, enet_uint32 ip) +{ + DEBUGCODE(string IP; iptoa(ip, IP)); + DEBUGVAR(IP); + p.put(ip); p.put(ip>>8); p.put(ip>>16); p.put(ip>>24); +} +void putip4(ucharbuf &p, enet_uint32 ip) { putip4_(p, ip); } +void putip4(packetbuf &p, enet_uint32 ip) { putip4_(p, ip); } +void putip4(vector &p, enet_uint32 ip) { putip4_(p, ip); } + +enet_uint32 getip4(ucharbuf &p) +{ + enet_uint32 ip = p.get() | (p.get()<<8) | (p.get()<<16) | (p.get()<<24); + DEBUGCODE(string IP; iptoa(ip, IP)); + DEBUGVAR(IP); + return ip; +} + +template +static inline void putuintn_(T &p, uint64_t val, int n) +{ + DEBUGVAR(val); + loopi(n) p.put(val >> (8 * i)); +} +void putuintn(ucharbuf &p, uint64_t val, int n) { putuintn_(p, val, n); } +void putuintn(packetbuf &p, uint64_t val, int n) { putuintn_(p, val, n); } +void putuintn(vector &p, uint64_t val, int n) { putuintn_(p, val, n); } + +uint64_t getuintn(ucharbuf &p, int n) +{ + uint64_t val = 0; + loopi(n) val |= p.get() << (8 * i); + DEBUGVAR(val); + return val; +} + #define GZMSGBUFSIZE ((MAXGZMSGSIZE * 11) / 10) static uchar *gzbuf = new uchar[GZMSGBUFSIZE]; // not thread-safe, but nesting is no problem @@ -207,12 +248,45 @@ void freegzbuf(ucharbuf *p) // free a ucharbuf created by getgzbuf() } } +//#define FILENAMESALLLOWERCASE + +bool validmapname(const char *s) // checks for length, allowed chars and special DOS filenames +{ + int len = strlen(s); + if(len > MAXMAPNAMELEN) return false; + if(len == 3 || len == 4) + { + char uc[4]; + loopi(3) uc[i] = toupper(s[i]); + uc[3] = '\0'; + const char *resd = "COMLPTCONPRNAUXNUL", *fnd = strstr(resd, uc); + if(fnd) + { + int pos = (int) (fnd - resd); + if(pos == 0 || pos == 3) + { + if(isdigit(s[3])) return false; // COMx, LPTx + } + else if(pos % 3 == 0) return false; // CON, PRN, AUX, NUL + } + } + while(*s != '\0') + { +#ifdef FILENAMESALLLOWERCASE + if(!islower(*s) && !isdigit(*s) && *s != '_' && *s != '-' && *s != '.') return false; +#else + if(!isalnum(*s) && *s != '_' && *s != '-' && *s != '.') return false; +#endif + ++s; + } + return true; +} + + // filter text according to rules // dst can be identical to src; dst needs to be of size "min(len, strlen(s)) + 1" // returns dst -//#define FILENAMESALLLOWERCASE - char *filtertext(char *dst, const char *src, int flags, int len) { char *res = dst; @@ -228,16 +302,16 @@ char *filtertext(char *dst, const char *src, int flags, int len) toupp = (flags & FTXT_TOUPPER) != 0, // translates to all-uppercase tolow = (flags & FTXT_TOLOWER) != 0, // translates to all-lowercase filename = (flags & FTXT_FILENAME) != 0, // strict a-z, 0-9 and "-_.()" (also translates "[]" and "{}" to "()"), removes everything between '<' and '>' - allowslash = (flags & FTXT_ALLOWSLASH) != 0, // only in combination with FTXT_FILENAME + allowslash = (flags & FTXT_ALLOWSLASH) != 0, // only in combination with FTXT_FILENAME or FTXT_MAPNAME mapname = (flags & FTXT_MAPNAME) != 0, // only allows lowercase chars, digits, '_', '-' and '.'; probably should be used in combination with TOLOWER - cropwhite = (flags & FTXT_CROPWHITE) != 0, // removes leading and trailing whitespace + cropwhitelead = (flags & FTXT_CROPWHITE_LEAD) != 0, // removes leading whitespace + cropwhitetrail = (flags & FTXT_CROPWHITE_TRAIL) != 0, // removes trailing whitespace pass = false; if(leet || mapname) nocolor = true; #ifdef FILENAMESALLLOWERCASE if(filename) tolow = true; #endif bool trans = toupp || tolow || leet || filename || fillblanks; - bool leadingwhite = cropwhite; char *lastwhite = NULL; bool insidepointybrackets = false; for(int c = *src; c; c = *++src) @@ -292,10 +366,10 @@ char *filtertext(char *dst, const char *src, int flags, int len) if(src[1]) ++src; continue; } - if(mapname && !isalnum(c) && !strchr("_-./\\", c)) continue; + if(mapname && !isalnum(c) && !strchr("_-.", c) && !(allowslash && strchr("/\\", c))) continue; if(isspace(c)) { - if(leadingwhite) continue; + if(cropwhitelead) continue; if(nowhite && !((c == ' ' && allowblanks) || (c == '\n' && allownl)) && !pass) continue; if(!lastwhite) lastwhite = dst; } @@ -304,11 +378,11 @@ char *filtertext(char *dst, const char *src, int flags, int len) lastwhite = NULL; if(!pass && !isprint(c)) continue; } - leadingwhite = false; + cropwhitelead = false; *dst++ = c; if(!--len || !*src) break; } - if(cropwhite && lastwhite) *lastwhite = '\0'; + if(cropwhitetrail && lastwhite) *lastwhite = '\0'; *dst = '\0'; return res; } @@ -335,6 +409,7 @@ void filterrichtext(char *dst, const char *src, int len) case 'a': c = '\a'; break; case 't': c = '\t'; break; case 'n': c = '\n'; break; + case '_': c = ' '; break; case 'x': b = 16; c = *++src; @@ -354,6 +429,37 @@ void filterrichtext(char *dst, const char *src, int len) *dst = '\0'; } +// counterpart of filterrichtext + +const char *escapestring(const char *s, bool force, bool noquotes, vector *buf) +{ + static vector strbuf[3]; + static int stridx = 0; + extern bool isdedicated; + if(noquotes) force = false; + if(!s) return force ? "\"\"" : ""; + if(!force && !*(s + strcspn(s, "\"/\\;()[] \f\t\a\n\r$"))) return s; + if(!buf) buf = &strbuf[stridx++]; + stridx %= 3; + buf->setsize(0); + if(!noquotes) buf->add('"'); + for(; *s; s++) switch(*s) + { + case '\n': buf->put("\\n", 2); break; + case '\r': buf->put("\\n", 2); break; + case '\t': buf->put("\\t", 2); break; + case '\a': buf->put("\\a", 2); break; + case '\f': buf->put("\\f", 2); break; + case '"': buf->put("\\\"", 2); break; + case '\\': buf->put("\\\\", 2); break; + case ' ': if(isdedicated) { buf->put("\\_", 2); break; } + default: buf->add(*s); break; + } + if(!noquotes) buf->add('"'); + buf->add(0); + return buf->getbuf(); +} + // ensures, that d is either two lowercase chars or "" void filterlang(char *d, const char *s) @@ -366,6 +472,17 @@ void filterlang(char *d, const char *s) *d = '\0'; } +void filtercountrycode(char *d, const char *s) // returns exactly two uppercase chars or "--" +{ + d[0] = d[1] = '-'; + d[2] = '\0'; + if(isalpha(s[0]) && isalpha(s[1]) && !s[2]) + { + d[0] = toupper(s[0]); + d[1] = toupper(s[1]); + } +} + void trimtrailingwhitespace(char *s) { for(int n = (int)strlen(s) - 1; n >= 0 && isspace(s[n]); n--) @@ -399,31 +516,94 @@ const char *mmfullname(int n) { return (n>=0 && n < MM_NUM) ? mmfullnames[n] : " int defaultgamelimit(int gamemode) { return m_teammode ? 15 : 10; } +int gmode_possible(bool hasffaspawns, bool hasteamspawns, bool hasflags) // return bitmask of playable modes, according to existing spawn and flag entities +{ + return ((hasffaspawns ? GMMASK__FFASPAWN : 0) | (hasteamspawns ? GMMASK__TEAMSPAWN : 0)) & ~(hasflags ? 0 : GMMASK__FLAGENTS); +} + +int gmode_parse(const char *list) // convert a list of mode acronyms to a bitmask +{ + char *buf = newstring(list), *b; + int res = 0; + for(char *p = strtok_r(buf, "|", &b); p; p = strtok_r(NULL, "|", &b)) + { + loopi(GMODE_NUM) if(!strcasecmp(p, modeacronymnames[i + 1])) res |= 1 << i; + } + delete buf; + return res; +} + +char *gmode_enum(int gm, char *buf) // convert mode bitmask to string with sorted list of mode acronyms +{ + vector mas; + loopi(GMODE_NUM) if(gm & (1 << i)) mas.add(modeacronymnames[i + 1]); + mas.sort(stringsortignorecase); + buf[0] = '\0'; + loopv(mas) concatformatstring(buf, "%s%s", i ? "|" : "", mas[i]); + filtertext(buf, buf, FTXT_TOLOWER); + return buf; +} + +int encodepitch(float p) // pitch value quantisation: use double resolution for -30°..30° and half resolution for -90°..-30° and 30°..90° +{ + const int thres = (1 << 24) / 3; + int r = (int)(p * (1 << 24)) / MAXPITCH; + if(r > thres) r = r / 4 + (1 << 22); + else if(r < -thres) r = r / 4 - (1 << 22); + r += (1 << 23) + (1 << (23 - PITCHBITS)); + if(r < 0) r = 0; + else if(r >= (1 << 24)) r = (1 << 24) - 1; + return r >> (24 - PITCHBITS); +} + +float decodepitch(int r) +{ + const int thres = (1 << 22) + (1 << 22) / 3; + r = (r << (24 - PITCHBITS)) - (1 << 23); + if(r > thres) r = r * 4 - thres * 3; + else if(r < -thres) r = r * 4 + thres * 3; + float p = float(r) * MAXPITCH / (1 << 24); + return clamp(p, -MAXPITCH, MAXPITCH); +} + +int encodeyaw(float y) // yaw value quantisation: simple rounded integer +{ + int r = (int)floorf(y * (1 << YAWBITS) / 360.0f + 0.5f); + return r & ((1 << YAWBITS) - 1); // modulo 360 degrees +} + +float decodeyaw(int r) +{ + r &= (1 << YAWBITS) - 1; + return float(r) / (1 << YAWBITS) * 360.0f; +} + static const int msgsizes[] = // size inclusive message token, 0 for variable or not-checked sizes { - SV_SERVINFO, 5, SV_WELCOME, 2, SV_INITCLIENT, 0, SV_POS, 0, SV_POSC, 0, SV_POSN, 0, SV_TEXT, 0, SV_TEAMTEXT, 0, SV_TEXTME, 0, SV_TEAMTEXTME, 0, SV_TEXTPRIVATE, 0, + SV_SERVINFO, 1, SV_SERVINFO_RESPONSE, 0, SV_SERVINFO_CONTD, 0, SV_WELCOME, 2, SV_INITCLIENT, 0, SV_POS, 0, SV_POSC, 0, SV_POSC2, 0, SV_POSC3, 0, SV_POSC4, 0, SV_POSN, 0, + SV_TEXT, 0, SV_TEAMTEXT, 0, SV_TEXTME, 0, SV_TEAMTEXTME, 0, SV_TEXTPRIVATE, 0, SV_SOUND, 2, SV_VOICECOM, 2, SV_VOICECOMTEAM, 2, SV_CDIS, 2, - SV_SHOOT, 0, SV_EXPLODE, 0, SV_SUICIDE, 1, SV_AKIMBO, 2, SV_RELOAD, 3, SV_AUTHT, 0, SV_AUTHREQ, 0, SV_AUTHTRY, 0, SV_AUTHANS, 0, SV_AUTHCHAL, 0, + SV_SHOOT, 0, SV_EXPLODE, 0, SV_SUICIDE, 1, SV_AKIMBO, 2, SV_RELOAD, 3, SV_GIBDIED, 5, SV_DIED, 5, SV_GIBDAMAGE, 7, SV_DAMAGE, 7, SV_HITPUSH, 6, SV_SHOTFX, 6, SV_THROWNADE, 8, SV_TRYSPAWN, 1, SV_SPAWNSTATE, 23, SV_SPAWN, 3, SV_SPAWNDENY, 2, SV_FORCEDEATH, 2, SV_RESUME, 0, SV_DISCSCORES, 0, SV_TIMEUP, 3, SV_EDITENT, 13, SV_ITEMACC, 2, SV_MAPCHANGE, 0, SV_ITEMSPAWN, 2, SV_ITEMPICKUP, 2, - SV_PING, 2, SV_PONG, 2, SV_CLIENTPING, 2, SV_GAMEMODE, 2, + SV_PING, 2, SV_PONG, 2, SV_CLIENTPING, 2, SV_EDITMODE, 2, SV_EDITXY, 8, SV_EDITARCH, 56, SV_EDITBLOCK, 0, SV_EDITD, 6, SV_EDITE, 6, SV_NEWMAP, 2, SV_SENDMAP, 0, SV_RECVMAP, 1, SV_REMOVEMAP, 0, - SV_SERVMSG, 0, SV_ITEMLIST, 0, SV_WEAPCHANGE, 2, SV_PRIMARYWEAP, 2, + SV_SERVMSG, 0, SV_SERVMSGVERB, 0, SV_ITEMLIST, 0, SV_WEAPCHANGE, 2, SV_PRIMARYWEAP, 2, SV_FLAGACTION, 3, SV_FLAGINFO, 0, SV_FLAGMSG, 0, SV_FLAGCNT, 3, SV_ARENAWIN, 2, SV_SETADMIN, 0, SV_SERVOPINFO, 3, SV_CALLVOTE, 0, SV_CALLVOTESUC, 1, SV_CALLVOTEERR, 2, SV_VOTE, 2, SV_VOTERESULT, 2, SV_SETTEAM, 3, SV_TEAMDENY, 2, SV_SERVERMODE, 2, - SV_IPLIST, 0, + SV_IPLIST, 0, SV_SPECTCN, 2, SV_LISTDEMOS, 1, SV_SENDDEMOLIST, 0, SV_GETDEMO, 2, SV_SENDDEMO, 0, SV_DEMOPLAYBACK, 3, SV_CONNECT, 0, SV_SWITCHNAME, 0, SV_SWITCHSKIN, 0, SV_SWITCHTEAM, 0, SV_CLIENT, 0, SV_EXTENSION, 0, - SV_MAPIDENT, 3, SV_HUDEXTRAS, 2, SV_POINTS, 0, + SV_MAPIDENT, 3, SV_DEMOCHECKSUM, 0, SV_DEMOSIGNATURE, 0, -1 }; @@ -438,3 +618,40 @@ int msgsizelookup(int msg) return msg >= 0 && msg < SV_NUM ? sizetable[msg] : -1; } +const char *disc_reason(int reason) +{ + static const char *prot[] = { "terminated by enet", "end of packet", "client num", "tag type", "duplicate connection", "overflow", "random", "voodoo", "sync", "auth failed" }, + *refused[] = { "connection refused", "due to ban", "due to blacklisting", "wrong password", "server FULL", "server mastermode is \"private\"", "server overloaded" }, + *removed[] = { "removed from server", "failed admin login", "inappropriate nickname", "annoyance limit exceeded", "SPAM", "DOS", "ludicrous speed", "fishslapped", "afk" }, + *kick[] = { "kicked", "banned", "kicked by vote", "banned by vote", "kicked by server operator", "banned by server operator", + "auto kicked - teamkilling", "auto banned - teamkilling", "auto kicked - friendly fire", "auto banned - friendly fire", "auto kick", "auto ban" }; + static string res; + const int prot_n = constarraysize(prot), ref_n = constarraysize(refused), rem_n = constarraysize(removed), kick_n = constarraysize(kick); + if(reason >= DISC_PROTOCOL && reason < DISC_REFUSED) + { // protocol related errors + if(reason < DISC_PROTOCOL + prot_n) return prot[reason]; + else formatstring(res)("network protocol error '%d'", reason); + } + else if(reason >= DISC_REFUSED && reason < DISC_REMOVED) + { // connection refused + if(reason < DISC_REFUSED + ref_n) return refused[reason - DISC_REFUSED]; + else formatstring(res)("connection refused '%d'", reason); + } + else if(reason >= DISC_REMOVED && reason < DISC_KICK) + { // player removed from server + if(reason < DISC_REMOVED + rem_n) return removed[reason - DISC_REMOVED]; + else formatstring(res)("removed from server '%d'", reason); + } + else if(reason >= DISC_KICK && reason < DISC_UNKNOWN) + { // kicks & bans + reason -= DISC_KICK; + if(reason < kick_n) return kick[reason]; + else formatstring(res)("%s '%d'", reason & 1 ? "banned" : "kicked", reason + DISC_KICK); + } + else + { // unknown reasons + formatstring(res)("unknown '%d'", reason); + } + return res; +} + diff --git a/source/src/protocol.h b/source/src/protocol.h index cefbb5a0..ae70d067 100644 --- a/source/src/protocol.h +++ b/source/src/protocol.h @@ -1,16 +1,20 @@ #define MAXCLIENTS 256 // in a multiplayer game, can be arbitrarily changed +#define MAXPLAYERS 32 // in a multiplayer game, can be arbitrarily changed #define DEFAULTCLIENTS 6 #define MAXTRANS 5000 // max amount of data to swallow in 1 go #define CUBE_DEFAULT_SERVER_PORT 28763 #define CUBE_SERVINFO_PORT_LAN 28762 #define CUBE_SERVINFO_PORT(serverport) (serverport+1) #define CUBE_SERVINFO_TO_SERV_PORT(servinfoport) (servinfoport-1) -#define PROTOCOL_VERSION 1201 // bump when protocol changes (use negative numbers for mods!) +#define CLIENTIPCENSOR 12 // blank out the last 12 bits of client's IPs before telling them to others (compromise between "16" from AC 0.9x and "8" from AC 1.1) +#define PROTOCOL_VERSION 1203 // bump when protocol changes (use negative numbers for mods!) #define DEMO_VERSION 2 // bump when demo format changes #define DEMO_MAGIC "ASSAULTCUBE_DEMO" #define DEMO_MINTIME 10000 // don't keep demo recordings with less than 10 seconds +#define MAXMAPNAMELEN 31 #define MAXMAPSENDSIZE 65536 #define MAXCFGFILESIZE 65536 +#define MAXDEMOSENDSIZE 10240001 #define MAXGZMSGSIZE 65536 #define MAXMEDIADOWNLOADFILESIZE 1024000 // hard cap on filesizes (raw and unzipped) - to limit the effect of zip bombs - no nice error messages: just cap #define MAXMODDOWNLOADSIZE 1024000 // hard cap on the filesize of downloaded mod packages - to keep stuff reasonable @@ -22,29 +26,29 @@ extern bool modprotocol; // network messages codes, c2s, c2c, s2c enum { - SV_SERVINFO = 0, SV_WELCOME, SV_INITCLIENT, SV_POS, SV_POSC, SV_POSN, SV_TEXT, SV_TEAMTEXT, SV_TEXTME, SV_TEAMTEXTME, SV_TEXTPRIVATE, + SV_SERVINFO = 0, SV_SERVINFO_RESPONSE, SV_SERVINFO_CONTD, SV_WELCOME, SV_INITCLIENT, SV_POS, SV_POSC, SV_POSC2, SV_POSC3, SV_POSC4, SV_POSN, SV_TEXT, SV_TEAMTEXT, SV_TEXTME, SV_TEAMTEXTME, SV_TEXTPRIVATE, SV_SOUND, SV_VOICECOM, SV_VOICECOMTEAM, SV_CDIS, - SV_SHOOT, SV_EXPLODE, SV_SUICIDE, SV_AKIMBO, SV_RELOAD, SV_AUTHT, SV_AUTHREQ, SV_AUTHTRY, SV_AUTHANS, SV_AUTHCHAL, + SV_SHOOT, SV_EXPLODE, SV_SUICIDE, SV_AKIMBO, SV_RELOAD, SV_GIBDIED, SV_DIED, SV_GIBDAMAGE, SV_DAMAGE, SV_HITPUSH, SV_SHOTFX, SV_THROWNADE, SV_TRYSPAWN, SV_SPAWNSTATE, SV_SPAWN, SV_SPAWNDENY, SV_FORCEDEATH, SV_RESUME, SV_DISCSCORES, SV_TIMEUP, SV_EDITENT, SV_ITEMACC, SV_MAPCHANGE, SV_ITEMSPAWN, SV_ITEMPICKUP, - SV_PING, SV_PONG, SV_CLIENTPING, SV_GAMEMODE, + SV_PING, SV_PONG, SV_CLIENTPING, SV_EDITMODE, SV_EDITXY, SV_EDITARCH, SV_EDITBLOCK, SV_EDITD, SV_EDITE, SV_NEWMAP, SV_SENDMAP, SV_RECVMAP, SV_REMOVEMAP, - SV_SERVMSG, SV_ITEMLIST, SV_WEAPCHANGE, SV_PRIMARYWEAP, + SV_SERVMSG, SV_SERVMSGVERB, SV_ITEMLIST, SV_WEAPCHANGE, SV_PRIMARYWEAP, SV_FLAGACTION, SV_FLAGINFO, SV_FLAGMSG, SV_FLAGCNT, SV_ARENAWIN, SV_SETADMIN, SV_SERVOPINFO, SV_CALLVOTE, SV_CALLVOTESUC, SV_CALLVOTEERR, SV_VOTE, SV_VOTERESULT, SV_SETTEAM, SV_TEAMDENY, SV_SERVERMODE, - SV_IPLIST, + SV_IPLIST, SV_SPECTCN, SV_LISTDEMOS, SV_SENDDEMOLIST, SV_GETDEMO, SV_SENDDEMO, SV_DEMOPLAYBACK, SV_CONNECT, SV_SWITCHNAME, SV_SWITCHSKIN, SV_SWITCHTEAM, SV_CLIENT, SV_EXTENSION, - SV_MAPIDENT, SV_HUDEXTRAS, SV_POINTS, + SV_MAPIDENT, SV_DEMOCHECKSUM, SV_DEMOSIGNATURE, SV_NUM }; @@ -65,15 +69,22 @@ enum { FA_PICKUP = 0, FA_STEAL, FA_DROP, FA_LOST, FA_RETURN, FA_SCORE, FA_NUM, F enum { FM_PICKUP = 0, FM_DROP, FM_LOST, FM_RETURN, FM_SCORE, FM_KTFSCORE, FM_SCOREFAIL, FM_RESET, FM_NUM }; enum { FTR_INFO = 0, FTR_PLAYERWISH, FTR_AUTOTEAM, FTR_SILENTFORCE, FTR_NUM }; // forceteam-reasons enum { SP_OK = 0, SP_REFILLMATCH, SP_OK_NUM, SP_WRONGMAP, SP_SPECT, SP_NUM }; // spawn permission ( &f, sqr *s, int len, bool preservesolids); extern bool rldecodecubes(ucharbuf &f, sqr *s, int len, int version, bool silent); extern void clearheaderextras(); @@ -916,7 +917,6 @@ extern void addgib(playerent *d); extern playerent *playerincrosshair(); extern int magsize(int gun); extern void setscope(bool activate); -extern void setburst(bool activate); extern void intersectgeometry(const vec &from, vec &to); extern int intersect(playerent *d, const vec &from, const vec &to, vec *end = NULL); extern bool intersect(entity *e, const vec &from, const vec &to, vec *end = NULL); @@ -928,6 +928,7 @@ extern int burstshotssettings[NUMGUNS]; // entities extern int edithideentmask; extern void pickupeffects(int n, playerent *d); +extern void rendereditentities(); extern void renderentities(); extern void rendermapmodels(); extern void resetpickups(int type = -1); @@ -953,6 +954,13 @@ extern const char *docgetdesc(const char *name); extern const char *acronymmodestr(int n); extern const char *fullmodestr(int n); extern int defaultgamelimit(int gamemode); +extern int gmode_possible(bool hasffaspawns, bool hasteamspawns, bool hasflags); +extern int gmode_parse(const char *list); +extern char *gmode_enum(int gm, char *buf); +extern int encodepitch(float p); +extern float decodepitch(int r); +extern int encodeyaw(float y); +extern float decodeyaw(int r); // crypto #define TIGERHASHSIZE 24 @@ -960,18 +968,13 @@ extern void tigerhash(uchar *hash, const uchar *msg, int len); extern void *tigerhash_init(uchar *hash); extern void tigerhash_add(uchar *hash, const void *msg, int len, void *state); extern void tigerhash_finish(uchar *hash, void *state); +extern const char *bin2hex(char *d, const uchar *s, int len); +extern int hex2bin(uchar *d, const char *s, int maxlen); +extern void ed25519_pubkey_from_private(uchar *pubkey, const uchar *privkey); +extern void ed25519_sign(uchar *sm, int *smlen, const uchar *m, int mlen, const uchar *sk); +extern uchar *ed25519_sign_check(uchar *sm, int smlen, const uchar *pk); +extern uchar *sk; // client game key extern void loadcertdir(); // load all certs in "config/certs" -#if 0 -// crypto // for AUTH -extern void genprivkey(const char *seed, vector &privstr, vector &pubstr); -extern bool hashstring(const char *str, char *result, int maxlen); -extern void answerchallenge(const char *privstr, const char *challenge, vector &answerstr); -extern void *parsepubkey(const char *pubstr); -extern void freepubkey(void *pubkey); -extern void *genchallenge(void *pubkey, const void *seed, int seedlen, vector &challengestr); -extern void freechallenge(void *answer); -extern bool checkchallenge(const char *answerstr, void *correct); -#endif // console extern void conoutf(const char *s, ...) PRINTFARGS(1, 2); @@ -1011,7 +1014,6 @@ extern void writecfg(); extern void deletecfg(); extern void identnames(vector &names, bool builtinonly); extern void explodelist(const char *s, vector &elems); -extern const char *escapestring(const char *s, bool force = true, bool noquotes = false); extern int execcontext; extern void pushscontext(int newcontext); extern int popscontext(); @@ -1022,14 +1024,18 @@ extern void dumpexecutionstack(stream *f); extern const char *currentserver(int i); // server +enum { /* timecodes */ VS_FIRSTLOGIN = 0, VS_LASTLOGIN, VS_MUTE, VS_NOVOTE, VS_BAN, VS_ADMIN, VS_OWNER, VS_MAPUPLOAD, + /* stats/counters */ VS_MINUTESCONNECTED, VS_MINUTESACTIVE, VS_FLAGS, VS_FRAGS, VS_DEATHS, VS_TKS, VS_DAMAGE, VS_FF, VS_NUM, VS_NUMCOUNTERS = VS_MINUTESCONNECTED }; +extern const char *vskeywords[VS_NUM + 1], *vsnames[VS_NUM + 1]; extern int modeacronyms; extern void servertoclient(int chan, uchar *buf, int len, bool demo = false); extern void localservertoclient(int chan, uchar *buf, int len, bool demo = false); extern const char *modestr(int n, bool acronyms = false); extern const char *voteerrorstr(int n); extern const char *mmfullname(int n); +extern void filterconfigset(char *list, void *cs = NULL); extern void fatal(const char *s, ...) PRINTFARGS(1, 2); -extern void initserver(bool dedicated, int argc = 0, char **argv = NULL); +extern void initserver(bool dedicated); extern void cleanupserver(); extern void localconnect(); extern void localdisconnect(); @@ -1055,20 +1061,33 @@ extern void sendstring(const char *t, ucharbuf &p); extern void sendstring(const char *t, packetbuf &p); extern void sendstring(const char *t, vector &p); extern void getstring(char *t, ucharbuf &p, int len = MAXTRANS); +extern void putip4(ucharbuf &p, enet_uint32 ip); +extern void putip4(packetbuf &p, enet_uint32 ip); +extern void putip4(vector &p, enet_uint32 ip); +extern enet_uint32 getip4(ucharbuf &p); +extern void putuintn(ucharbuf &p, uint64_t val, int n); +extern void putuintn(packetbuf &p, uint64_t val, int n); +extern void putuintn(vector &p, uint64_t val, int n); +extern uint64_t getuintn(ucharbuf &p, int n); extern void putgzbuf(vector &d, vector &s); // zips a vector into a stream, stored in another vector extern ucharbuf *getgzbuf(ucharbuf &p); // fetch a gzipped buffer; needs to be freed properly later extern void freegzbuf(ucharbuf *p); // free a ucharbuf created by getgzbuf() +extern bool validmapname(const char *s); // checks for length, allowed chars and special DOS filenames extern char *filtertext(char *dst, const char *src, int flags, int len = sizeof(string)-1); extern void filterrichtext(char *dst, const char *src, int len = sizeof(string)-1); +extern const char *escapestring(const char *s, bool force = true, bool noquotes = false, vector *buf = NULL); extern void filterlang(char *d, const char *s); +extern void filtercountrycode(char *d, const char *s); // returns exactly two uppercase chars or "--" extern void trimtrailingwhitespace(char *s); +extern int msgsizelookup(int msg); +extern const char *disc_reason(int reason); extern string mastername; extern int masterport; +extern bool usemaster; extern ENetSocket connectmaster(); -extern void serverms(int mode, int numplayers, int minremain, char *smapname, int millis, const ENetAddress &localaddr, int *mnum, int *msend, int *mrec, int *cnum, int *csend, int *crec, int protocol_version); -extern int msgsizelookup(int msg); +extern void serverms(int mode, int numplayers, int minremain, char *smapname, int millis, const ENetAddress &localaddr, int *mnum, int *msend, int *mrec, int *cnum, int *csend, int *crec, int protocol_version, const char *servdesccur, int _interm); extern const char *genpwdhash(const char *name, const char *pwd, int salt); -extern void servermsinit(const char *master, const char *ip, int serverport, bool listen); +extern void servermsinit(bool listen); extern bool serverpickup(int i, int sender); extern bool valid_client(int cn); extern void extinfo_cnbuf(ucharbuf &p, int cn); @@ -1078,6 +1097,7 @@ extern char *votestring(int type, char *arg1, char *arg2, char *arg3); extern int wizardmain(int argc, char **argv); // some tools +extern header *peekmapheader(uchar *data, int len); extern servsqr *createservworld(const sqr *s, int _cubicsize); extern int calcmapdims(mapdim_s &md, const servsqr *s, int _ssize); extern int calcmapareastats(mapareastats_s &ms, servsqr *s, int _ssize, const mapdim_s &md); @@ -1093,8 +1113,6 @@ struct demoheader char desc[DHDR_DESCCHARS]; char plist[DHDR_PLISTCHARS]; }; -#define DEFDEMOFILEFMT "%w_%h_%n_%Mmin_%G" -#define DEFDEMOTIMEFMT "%Y%m%d_%H%M" extern bool watchingdemo; extern int demoprotocol; @@ -1104,9 +1122,13 @@ extern void enddemoplayback(); enum { ACLOG_DEBUG = 0, ACLOG_VERBOSE, ACLOG_INFO, ACLOG_WARNING, ACLOG_ERROR, ACLOG_NUM }; -extern bool initlogging(const char *identity, int facility_, int consolethres, int filethres, int syslogthres, bool logtimestamp); +extern bool initlogging(const char *identity, int facility_, int consolethres, int filethres, int syslogthres, bool logtimestamp, const char *logfilepath); extern void exitlogging(); -extern bool logline(int level, const char *msg, ...) PRINTFARGS(2, 3); +extern bool logcheck(int level); +extern void mlog(int level, const char *msg, ...) PRINTFARGS(2, 3); // log line from main thread (never blocks) +extern void tlog(int level, const char *msg, ...) PRINTFARGS(2, 3); // log line from other-than-main thread (may block) +extern void xlog(int level, const char *msg, ...) PRINTFARGS(2, 3); // log line from any thread (more expensive: only use for functions, that are called from main and other threads) +extern void poll_logbuffers(); // server config @@ -1115,35 +1137,40 @@ struct serverconfigfile string filename; int filelen; char *buf; - serverconfigfile() : filelen(0), buf(NULL) { filename[0] = '\0'; } + uint32_t filehash; + sl_semaphore busy; + bool readfailed, updated; + serverconfigfile() : filelen(0), buf(NULL), busy(1, NULL) { filename[0] = '\0'; fnv1a_init(filehash); } virtual ~serverconfigfile() { DELETEA(buf); } + virtual void clear() {} virtual void read() {} - void init(const char *name); - bool load(); + void init(const char *name, bool tracking = true); + virtual bool load(); + bool isbusy(); + void trypreload(); + void process(); }; // server commandline parsing struct servercommandline { - int uprate, serverport, syslogfacility, filethres, syslogthres, maxdemos, maxclients, kickthreshold, banthreshold, verbose, incoming_limit, afk_limit, ban_time, demotimelocal; - const char *ip, *master, *logident, *serverpassword, *adminpasswd, *demopath, *maprot, *pwdfile, *blfile, *nbfile, *infopath, *motdpath, *forbidden, *demofilenameformat, *demotimestampformat; + int uprate, serverport, syslogfacility, filethres, syslogthres, maxdemos, maxclients, kickthreshold, banthreshold, verbose, incoming_limit, afk_limit, ban_time; + const char *ip, *master, *logident, *serverpassword, *adminpasswd, *demopath, *maprotfile, *pwdfile, *blfile, *geoipfile, *nbfile, *infopath, *motdpath, *forbidden, *service, *logfilepath, *parfilepath, *ssk, *vitabasename; bool logtimestamp, demo_interm, loggamestatus; string motd, servdesc_full, servdesc_pre, servdesc_suf, voteperm, mapperm; int clfilenesting; - vector adminonlymaps; + vector adminonlymaps; // FIXME: remove this - servercommandline() : uprate(0), serverport(CUBE_DEFAULT_SERVER_PORT), syslogfacility(6), filethres(-1), syslogthres(-1), maxdemos(5), - maxclients(DEFAULTCLIENTS), kickthreshold(-5), banthreshold(-6), verbose(0), incoming_limit(10), afk_limit(45000), ban_time(20*60*1000), demotimelocal(0), - ip(""), master(NULL), logident(""), serverpassword(""), adminpasswd(""), demopath(""), - maprot("config/maprot.cfg"), pwdfile("config/serverpwd.cfg"), blfile("config/serverblacklist.cfg"), nbfile("config/nicknameblacklist.cfg"), - infopath("config/serverinfo"), motdpath("config/motd"), forbidden("config/forbidden.cfg"), + servercommandline() : uprate(0), serverport(CUBE_DEFAULT_SERVER_PORT), syslogfacility(6), filethres(-1), syslogthres(-1), maxdemos(-1), + maxclients(DEFAULTCLIENTS), kickthreshold(-5), banthreshold(-6), verbose(0), incoming_limit(10), afk_limit(45000), ban_time(20*60*1000), + ip(""), master(NULL), logident(""), serverpassword(""), adminpasswd(""), demopath(NULL), + maprotfile("config/maprot.cfg"), pwdfile("config/serverpwd.cfg"), blfile("config/serverblacklist.cfg"), geoipfile("config/geoip.cfg"), nbfile("config/nicknameblacklist.cfg"), + infopath("config/serverinfo"), motdpath("config/motd"), forbidden("config/forbidden.cfg"), service(NULL), logfilepath("logs/"), parfilepath("config/serverparameters.cfg"), ssk(NULL), vitabasename("config/servervita"), logtimestamp(false), demo_interm(false), loggamestatus(true), clfilenesting(0) { motd[0] = servdesc_full[0] = servdesc_pre[0] = servdesc_suf[0] = voteperm[0] = mapperm[0] = '\0'; - demofilenameformat = DEFDEMOFILEFMT; - demotimestampformat = DEFDEMOTIMEFMT; } bool checkarg(const char *arg) @@ -1154,22 +1181,10 @@ struct servercommandline int ai = atoi(a); // client: dtwhzbsave switch(arg[1]) - { // todo: gjlqEGHJQUYZ + { // todo: gjlqEJQU + case 'Y': ssk = a; break; // private server key case '-': - if(!strncmp(arg, "--demofilenameformat=", 21)) - { - demofilenameformat = arg+21; - } - else if(!strncmp(arg, "--demotimestampformat=", 22)) - { - demotimestampformat = arg+22; - } - else if(!strncmp(arg, "--demotimelocal=", 16)) - { - int ai = atoi(arg+16); - demotimelocal = ai == 0 ? 0 : 1; - } - else if(!strncmp(arg, "--masterport=", 13)) + if(!strncmp(arg, "--masterport=", 13)) { int ai = atoi(arg+13); masterport = ai == 0 ? AC_MASTER_PORT : ai; @@ -1191,7 +1206,9 @@ struct servercommandline case 'S': syslogthres = atoi(a + 1); break; } break; - case 'A': if(*a) adminonlymaps.add(a); break; + case 'H': logfilepath = a; break; + case 'A': parfilepath = a; break; + case 'U': vitabasename = a; break; case 'c': if(ai > 0) maxclients = min(ai, MAXCLIENTS); break; case 'k': { @@ -1221,9 +1238,10 @@ struct servercommandline break; } case 'W': demopath = a; break; - case 'r': maprot = a; break; + case 'r': maprotfile = a; break; case 'X': pwdfile = a; break; case 'B': blfile = a; break; + case 'G': geoipfile = a; break; case 'K': nbfile = a; break; case 'I': infopath = a; break; case 'o': filterrichtext(motd, a); break; @@ -1244,11 +1262,12 @@ struct servercommandline case 'P': concatstring(voteperm, a); break; case 'M': concatstring(mapperm, a); break; case 'Z': if(ai >= 0) incoming_limit = ai; break; + case 'S': service = a; break; case 'V': verbose++; break; case 'C': if(*a && clfilenesting < 3) { serverconfigfile cfg; - cfg.init(a); + cfg.init(a, false); cfg.load(); int line = 1; clfilenesting++; diff --git a/source/src/rendergl.cpp b/source/src/rendergl.cpp index 7c861e41..3c67d00e 100644 --- a/source/src/rendergl.cpp +++ b/source/src/rendergl.cpp @@ -469,16 +469,22 @@ void fovchanged(); FVARFP(fov, 75, 90, 120, fovchanged()); VARFP(scopefov, 5, 50, 60, fovchanged()); VARP(spectfov, 5, 110, 120); +VARP(spectfovremote, 0, 0, 1); // use spectfov or remote player's fov when spectating void fovchanged() { extern float autoscopesensscale; autoscopesensscale = tan(((float)scopefov)*0.5f*RAD)/tan(fov*0.5f*RAD); + player1->ffov = (int)fov; + player1->scopefov = scopefov; } float dynfov() { - if(player1->weaponsel->type == GUN_SNIPER && ((sniperrifle *)player1->weaponsel)->scoped) return (float)scopefov; - else if(player1->isspectating()) return (float)spectfov; + bool isscoped = player1->weaponsel->type == GUN_SNIPER && ((sniperrifle *)player1->weaponsel)->scoped; + bool useremote = spectfovremote && camera1 != player1 && camera1->type == ENT_PLAYER && ((playerent *)camera1)->ffov && ((playerent *)camera1)->scopefov; + if(camera1 != player1 && camera1->type < ENT_CAMERA) isscoped = ((playerent *)camera1)->scoping; + if(isscoped) return (float) (useremote ? ((playerent *)camera1)->scopefov : scopefov); + else if(player1->isspectating()) return (float)(useremote ? ((playerent *)camera1)->ffov : spectfov); else return (float)fov; } @@ -851,7 +857,6 @@ void drawminimap(int w, int h) glDepthFunc(GL_LESS); rendermapmodels(); renderzones(clmapdims.maxceil); - //renderentities();// IMHO better done by radar itself, if at all resettmu(0); float hf = waterlevel - 0.3f; renderwater(hf, 0, 0); @@ -1105,7 +1110,7 @@ void gl_drawframe(int w, int h, float changelod, float curfps, int elapsed) if(effective_stencilshadow && hasstencil && stencilbits >= 8) drawstencilshadows(); startmodelbatches(); - renderentities(); + rendereditentities(); endmodelbatches(); readdepth(w, h, worldpos); @@ -1115,6 +1120,7 @@ void gl_drawframe(int w, int h, float changelod, float curfps, int elapsed) endmodelbatches(); startmodelbatches(); + renderentities(); renderbounceents(); endmodelbatches(); diff --git a/source/src/renderhud.cpp b/source/src/renderhud.cpp index 6cb6c69c..6ed0ea39 100644 --- a/source/src/renderhud.cpp +++ b/source/src/renderhud.cpp @@ -187,7 +187,7 @@ void loadcrosshair(const char *type, const char *filename) } else if(strchr(type, '.')) { // old syntax "loadcrosshair filename type", remove this in 2020 - const char *oldcrosshairnames[CROSSHAIR_NUM + 1] = { "default", "teammate", "scope", "knife", "pistol", "carbine", "shotgun", "smg", "sniper", "ar", "cpistol", "grenades", "akimbo", "" }; + const char *oldcrosshairnames[CROSSHAIR_NUM + 1] = { "default", "teammate", "scope", "knife", "pistol", "carbine", "shotgun", "smg", "sniper", "ar", "grenades", "akimbo", "" }; index = getlistindex(filename, oldcrosshairnames, false, 0); if(index > 2) index -= 3; else index += NUMGUNS; @@ -338,7 +338,7 @@ void drawequipicons(playerent *p) // weapons int c = p->weaponsel->type != GUN_GRENADE ? p->weaponsel->type : getprevweaponsel(p), r = 0; - if(c==GUN_AKIMBO || c==GUN_CPISTOL) c = GUN_PISTOL; // same icon for akimb & pistol + if(c==GUN_AKIMBO) c = GUN_PISTOL; // same icon for akimb & pistol if(c>3) { c -= 4; r = 1; } if(p->weaponsel && valid_weapon(p->weaponsel->type)) drawequipicon(HUDPOS_WEAPON*2, 1650, c, r); @@ -724,51 +724,6 @@ inline char rangecolor(int val, const char *colors, int thres1, int thres2, int return colors[3]; } -void drawmedals(float x, float y, int col, int row, Texture *tex) -{ - if(tex) - { - glPushAttrib(GL_COLOR_BUFFER_BIT); - glDisable(GL_BLEND); - drawicon(tex, x, y, 120, col, row, 1/4.0f); - glPopAttrib(); - } -} -const char *medal_str[] = -{ - "Best Fragger", "Dude that dies a lot" -}; //just some medals string tests, nothing serious -extern bool medals_arrived; -extern medalsst a_medals[END_MDS]; -void drawscores() -{ - static float time=0; - if(!medals_arrived) {time=0; return;} else if(time > 5){time=0; medals_arrived=0;} - static Texture *tex = NULL; - if(!tex) tex = textureload("packages/misc/nice_medals.png", 4); - time+=((float)(curtime))/1000; - float vw=VIRTW*7/4,vh=VIRTH*7/4; - glPushAttrib(GL_COLOR_BUFFER_BIT); - glLoadIdentity(); - glOrtho(0, vw, vh, 0, -1, 1); - int left = vw/4, top = vh/4; - blendbox(left, top, left*3, top*3, true, -1); - top+=10;left+=10;const float txtdx=160,txtdy=30,medalsdy=130; - glColor4f(1,1,1,1); - float desttime=0; - loopi(END_MDS) { - if(a_medals[i].assigned) { - desttime+=0.3; - if(time < desttime) continue; - drawmedals(left, top, 0, 0, tex); - playerent *mpl = getclient(a_medals[i].cn); - draw_textf("%s %s: %d", left+txtdx, top+txtdy, medal_str[i], mpl->name, a_medals[i].item); top+=medalsdy; - } - } - - glPopAttrib(); -} - string enginestateinfo = ""; COMMANDF(getEngineState, "", () { result(enginestateinfo); }); @@ -1025,7 +980,6 @@ void gl_drawhud(int w, int h, int curfps, int nquads, int curvert, bool underwat glPopMatrix(); } - drawscores(); if(!hidespecthud && spectating && player1->spectatemode!=SM_DEATHCAM) { glLoadIdentity(); diff --git a/source/src/rendermodel.cpp b/source/src/rendermodel.cpp index 4967c93e..aea3c3ee 100644 --- a/source/src/rendermodel.cpp +++ b/source/src/rendermodel.cpp @@ -751,9 +751,6 @@ void endmodelbatches(bool flush) if(flush) clearmodelbatches(); } -const int dbgmbatch = 0; -//VAR(dbgmbatch, 0, 0, 1); - VARP(popdeadplayers, 0, 0, 1); void rendermodel(const char *mdl, int anim, int tex, float rad, const vec &o, float roll, float yaw, float pitch, float speed, int basetime, playerent *d, modelattach *a, float scale) { @@ -797,7 +794,7 @@ void rendermodel(const char *mdl, int anim, int tex, float rad, const vec &o, fl //if(a[i].m && a[i].m->type()!=m->type()) a[i].m = NULL; } - if(numbatches>=0 && !dbgmbatch) + if(numbatches>=0) { batchedmodel &b = addbatchedmodel(m); b.o = o; @@ -819,7 +816,7 @@ void rendermodel(const char *mdl, int anim, int tex, float rad, const vec &o, fl if(stenciling) { m->startrender(); - m->render(anim|ANIM_NOSKIN, varseed, speed, basetime, o, 0, yaw, pitch, d, a, scale); + m->render(anim|ANIM_NOSKIN, varseed, speed, basetime, o, roll, yaw, pitch, d, a, scale); m->endrender(); return; } @@ -870,7 +867,7 @@ void rendermodel(const char *mdl, int anim, int tex, float rad, const vec &o, fl glColor4f(color[0], color[1], color[2], m->translucency); } - m->render(anim, varseed, speed, basetime, o, 0, yaw, pitch, d, a, scale); + m->render(anim, varseed, speed, basetime, o, roll, yaw, pitch, d, a, scale); if(anim&ANIM_TRANSLUCENT) { @@ -916,7 +913,6 @@ void preload_playermodels() if(dynshadow && playermdl) playermdl->genshadows(8.0f, 4.0f); loopi(NUMGUNS) { - if(i == GUN_CPISTOL) continue; defformatstring(vwep)("weapons/%s/world", guns[i].modelname); model *vwepmdl = loadmodel(vwep); if(dynshadow && vwepmdl) vwepmdl->genshadows(8.0f, 4.0f); diff --git a/source/src/server.cpp b/source/src/server.cpp index 18b3c092..745c193e 100644 --- a/source/src/server.cpp +++ b/source/src/server.cpp @@ -13,78 +13,130 @@ VARP(serverdebug, 0, 0, 1); #include "server.h" #include "servercontroller.h" #include "serverfiles.h" -// 2011feb05:ft: quitproc + #include "signal.h" +struct servergame +{ + int mastermode; + bool autoteam; + int matchteamsize; + bool forceintermission; + string servdesc_current; + ENetAddress servdesc_caller; + bool custom_servdesc; + + // current game + string smapname, nextmapname; + int smode, nextgamemode, srvgamesalt; + int interm; + int minremain, gamemillis, gamelimit, nextsendscore; + int arenaround, arenaroundstartmillis; + int lastfillup; + vector sents; + sflaginfo sflaginfos[2]; + + bool recordpackets; + int demosequence; // # of demo to fetch with F10 (usually curent game, until one minute after it finished) + + servermap *curmap; + char *layout; // NULL: edit mode or singleplayer without properly loaded map + uchar *coop_mapdata; + int coop_cgzlen, coop_cfglen, coop_cfglengz; + + void init() + { + mastermode = MM_OPEN; + autoteam = true; + matchteamsize = 0; + forceintermission = false; + custom_servdesc = false; + smode = 0; + interm = 0; + minremain = 0; + gamemillis = 0; + gamelimit = 0; + nextsendscore = 0; + arenaround = 0; + arenaroundstartmillis = 0; + lastfillup = 0; + sents.setsize(0); + loopi(2) sflaginfos[i].actor_cn = -1; + recordpackets = false; + demosequence = 0; + curmap = NULL; + layout = NULL; + coop_mapdata = NULL; + coop_cgzlen = coop_cfglen = coop_cfglengz = 0; + } + + void free() + { + DELETEA(layout); + DELETEA(coop_mapdata); + } +} cursgame, *sg = &cursgame; + // config -vector servermaps; // all available maps kept in memory +vector servermaps, gamesuggestions; // all available maps kept in memory +string servpubkey; +uchar ssk[64] = { 0 }, *spk = ssk + 32; +enet_uint32 servownip; // IP address of this server, as seen by the master or other clients/servers servercontroller *svcctrl = NULL; +serverparameter serverparameters; servercommandline scl; servermaprot maprot; serveripblacklist ipblacklist; +serveripcclist geoiplist; servernickblacklist nickblacklist; serverforbiddenlist forbiddenlist; serverpasswords passwords; -serverinfofile infofiles; +serverinfofile serverinfoinfo; +serverinfofile serverinfomotd; // server state bool isdedicated = false; ENetHost *serverhost = NULL; -int laststatus = 0, servmillis = 0, lastfillup = 0; +int laststatus = 0, // last per-minute log status + servmillis = 0, // millis since server start + servclock = 0; // unixtime in minutes vector clients; vector worldstates; vector savedscores; vector bans; -vector demofiles; - -int mastermode = MM_OPEN; -static bool autoteam = true; -int matchteamsize = 0; long int incoming_size = 0; -static bool forceintermission = false; - -string servdesc_current; -ENetAddress servdesc_caller; -bool custom_servdesc = false; - -// current game -string smapname, nextmapname; -int smode = 0, nextgamemode; -int interm = 0; -static int minremain = 0, gamemillis = 0, gamelimit = 0, /*lmsitemtype = 0,*/ nextsendscore = 0; -mapstats smapstats; -vector sents; -char *maplayout = NULL, *testlayout = NULL; -int maplayout_factor, testlayout_factor, maplayoutssize; -servermapbuffer mapbuffer; - // cmod -char *global_name; int totalclients = 0; int cn2boot; -int servertime = 0, serverlagged = 0; // synchronising the worker threads... +const char *endis[] = { "disabled", "enabled", "" }; +SERVPARLIST(dumpmaprot, 0, 1, 0, endis, "ddump maprot parameters for all maps (once, to logs/debug/maprot_debug_verbose.txt)"); +SERVPARLIST(dumpparameters, 0, 0, 0, endis, "ddump server parameters when updated"); +SERVPAR(vitaautosave, 0, 10, 24 * 60, "sVita file autosave interval in minutes (0: only when server is empty)"); +SERVPAR(vitamaxage, 3, 12, 120, "sOmit vitas from autosave, if the last login has been more than the specified number of months ago"); void poll_serverthreads() // called once per mainloop-timeslice { static vector servermapstodelete; - static int stage = 0, lastworkerthreadstart = 0; + static int stage = 0, lastworkerthreadstart = 0, nextserverconfig = 0, lastvitasave = 0; + static bool debugmaprot = true, serverwasnotempty = false; - switch(stage) + switch(stage) // cycle through some tasks once per minute (mostly file polling) { default: // start first thread { if(startnewservermapsepoch || readmapsthread_sem->getvalue()) fatal("thread management mishap"); // zero tolerance... // wake readmapsthread - logline(ACLOG_INFO,"waking readmapsthread"); + mlog(ACLOG_DEBUG,"waking readmapsthread"); startnewservermapsepoch = true; + servclock = (int) (time(NULL) / (time_t) 60); readmapsthread_sem->post(); stage = 1; lastworkerthreadstart = servmillis; @@ -102,15 +154,18 @@ void poll_serverthreads() // called once per mainloop-timeslice { if(!strcmp(servermaps[i]->fname, fresh->fname)) // we don't check paths here - map filenames have to be unique { // found map of same name - logline(ACLOG_INFO,"marked servermap %s%s for deletion", servermaps[i]->fpath, servermaps[i]->fname); + mlog(ACLOG_INFO,"marked servermap %s%s for deletion", servermaps[i]->fpath, servermaps[i]->fname); servermapstodelete.add(servermaps.remove(i)); // mark old version for deletion } } if(fresh->isok) { servermaps.add(fresh); - logline(ACLOG_INFO,"added servermap %s%s", fresh->fpath, fresh->fname); + mlog(ACLOG_INFO,"added servermap %s%s", fresh->fpath, fresh->fname); + maprot.initmap(fresh, NULL); + servermaps.sort(servermapsortname); // keep list sorted at all times } + else delete fresh; servermapdropbox = NULL; } readmapsthread_sem->post(); @@ -124,36 +179,205 @@ void poll_serverthreads() // called once per mainloop-timeslice } break; } - - case 2: // wake readconfigsthread -#if 0 + case 2: // wake readserverconfigsthread { - readallconfigfiles = true; - readconfigsthread_sem.post(); + recheckallserverconfigs = true; + nextserverconfig = 0; + readserverconfigsthread_sem->post(); stage++; break; } - - case 3: // readconfigsthread + case 3: // readserverconfigsthread { - stage++; + if(!recheckallserverconfigs) + { + if(serverconfigs.inrange(nextserverconfig)) + { + serverconfigs[nextserverconfig]->process(); // processing of config files is done in mainloop, one at a time + nextserverconfig++; + } + else + { + if(vitaupdatebuf) + { + mlog(ACLOG_INFO, "read/updated %d vitas from %s", parsevitas(vitaupdatebuf, vitaupdatebuflen), vitafilename_update_backup);; + DELETEA(vitaupdatebuf); + } + stage++; + } + } break; } + case 4: // check for fresh config files + { + if(maprot.updated) + { + maprot.updated = false; + defformatstring(fname)("%sdebug/maprot_debug_verbose.txt", scl.logfilepath); + stream *f = debugmaprot && dumpmaprot ? openfile(path(fname), "w") : NULL; + loopv(servermaps) maprot.initmap(servermaps[i], f); // may stall a few msec if there are very many maps + DELETEP(f); + debugmaprot = false; // only once during server start + } + else if(serverparameters.updated) + { + serverparameters.updated = false; + if(dumpparameters) + { + defformatstring(fname)("%sdebug/serverparameter_dump_%s.txt", scl.logfilepath, numtime()); + stream *f = openfile(path(fname), "w"); + f->printf("// server parameter dump, %s\n\n", asctimestr()); + serverparameters.dump(f); + delete f; + } + } + else if(!vitastosave && serverwasnotempty && (numclients() == 0 || (vitaautosave && (servmillis - lastvitasave) > 1000 * 60 * vitaautosave))) + { + serverwasnotempty = false; + lastvitasave = servmillis; + vitastosave = new vector; + vitakey_s vk; + int too_old = 0; + enumeratekt(vitas, uchar32, k, vita_s, v, + { + if((servclock - v.vs[VS_LASTLOGIN]) / (60 * 24 * 31) <= vitamaxage) + { + vk.k = &k; + vk.v = &v; + vitastosave->add(vk); + } + else too_old++; + }); + if(too_old) mlog(ACLOG_VERBOSE, "omitted %d vitas from autosave (exceeding maxage of %d months)", too_old, vitamaxage); + } + else stage++; + break; + } + case 5: // log changed server diagnostics parameters + serverparameters.log(); + stage++; + break; - case 4: // pause worker threads for a while (restart once a minute) -#endif + case 6: // pause worker threads for a while (restart once a minute) { if(servmillis - lastworkerthreadstart > 60 * 1000) stage = 0; else if(numclients() == 0) { // empty server and nothing to do: - loopvrev(servermapstodelete) delete servermapstodelete.remove(i); // delete outdated servermaps + loopvrev(servermapstodelete) + { + gamesuggestions.removeobj(servermapstodelete[i]); + delete servermapstodelete.remove(i); // delete outdated servermaps + } } + else serverwasnotempty = true; break; } } + + // update thread-safe data structures (vectors and hashtables can be read concurrently - but to be updated, they need to be locked) + poll_logbuffers(); + + +} + +SERVPAR(gamepenalty_cutoff, 30, 60, 120, "gNumber of minutes to remember that a map+mode combination has been played"); +SERVPAR(gamepenalty_random, 1, 1, 60, "gAmount of random weight to add to each map+mode combination"); +SERVPAR(mappenalty_cutoff, 45, 60, 240, "gNumber of minutes to remember that a map has been played"); +SERVPAR(mappenalty_weight, 0, 100, 200, "gInfluence of play history of a map (0: no influence, 100: normal, 200: high influence)"); +SERVPAR(modepenalty_weight, -100, 50, 100, "gInfluence of play history of a game mode (-100: stay with played mode, 0: ignore played mode, 100: suggest other modes)"); + +void servermaprot::calcmappenalties() // sum up, how long ago each map+mode was played -> get individual penalties for every map+mode combination +{ + int modecutoff = gamepenalty_cutoff * 60, mapcutoff = mappenalty_cutoff * 60; + loopv(servermaps) + { + servermap &s = *servermaps[i]; + s.mappenalty = 0; + loopi(GMODE_NUM) s.penalty[i] = 0; + loopi(GAMEHISTLEN) if(s.lastplayed[i] && ((1 << s.lastmodes[i]) & s.modes_allowed)) + { + int d = (servmillis - s.lastplayed[i]) / 1000, m = s.lastmodes[i]; + if(d < modecutoff) + { + s.penalty[m] += ((modecutoff - d + 119) * PENALTYUNIT) / modecutoff; // last minute -> 1 PENALTYUNIT, "cutoff" minutes ago -> 0 PENALTYUNIT, linear inbetween + } + if(d < mapcutoff) + { + s.mappenalty += ((mapcutoff - d + 59) * PENALTYUNIT) / mapcutoff; // last minute -> 1 PENALTYUNIT, "cutoff" minutes ago -> 0 PENALTYUNIT, linear inbetween + } + } + loopi(GMODE_NUM) if((1 << i) & s.modes_allowed) + { + s.penalty[i] = (s.penalty[i] * (100 - s.modes[i].repeat)) / 100; + s.penalty[i] -= s.modes[i].weight * PENALTYUNIT; + s.penalty[i] += rnd(gamepenalty_random * PENALTYUNIT); + } + } } +servermap *randommap() +{ + if(servermaps.length() < 1) fatal("no maps available"); + servermap *s = servermaps[rnd(servermaps.length())]; + s->bestmode = 0; // tdm + return s; +} +SERVPARLIST(dumpsuggestions, 0, 1, 0, endis, "ddump maprot suggestions whenever recalculated"); + +servermap *servermaprot::recalcgamesuggestions(int numpl) // regenerate list of playable games (map+mode) +{ + calcmodepenalties(modepenalty_weight); + calcmappenalties(); + gamesuggestions.setsize(0); + if(numpl >= MAXPLAYERS) numpl = MAXPLAYERS - 1; + loopv(servermaps) + { + servermap &s = *servermaps[i]; + s.bestmode = -1; + s.weight = INT_MIN; + int bestpen = INT_MAX; + int modemask = (numpl < 0 ? s.modes_allowed : s.modes_pn[numpl]) & s.modes_auto; + loopi(GMODE_NUM) if(modemask & (1 << i)) + { + if((s.penalty[i] + modepenalty[i]) < bestpen) + { + bestpen = s.penalty[i] + modepenalty[i]; + s.bestmode = i; + } + } + if(s.bestmode >= 0) + { + s.weight = -bestpen - ((mappenalty_weight + 1) * s.mappenalty) / 100; + gamesuggestions.add(&s); + } + } + gamesuggestions.sort(servermapsortweight); + if(dumpsuggestions) + { + defformatstring(fname)("%sdebug/maprotsuggestions_dump_%s.txt", scl.logfilepath, numtime()); + stream *f = openfile(path(fname), "w"); + f->printf("// maprot suggestions dump, %s\n\nmode penalties:", asctimestr()); + string b; + loopj(GMODE_NUM) if((1 << j) & GMMASK__MPNOCOOP) f->printf(" %s:%d", gmode_enum(1 << j, b), modepenalty[j]); + f->printf("\n\n"); + loopv(gamesuggestions) + { + servermap &s = *gamesuggestions[i]; + f->printf("map: \"%s%s\", weight: %d, bestmode: %d (%s), mappenalty: %d\n mode penalties:", s.fpath, s.fname, s.weight, s.bestmode, gmode_enum(1 << s.bestmode, b), s.mappenalty); + loopj(GMODE_NUM) if(s.penalty[j]) f->printf(" %s:%d", gmode_enum(1 << j, b), s.penalty[j]); + f->printf("\n\n"); + } + delete f; + } + return gamesuggestions.length() ? gamesuggestions[0] : randommap(); +} + +servermap *getservermap(const char *mapname) // check, if the server knows a map (by filename) +{ + servermap *k = (servermap *)&mapname, **res = servermaps.search(&k, servermapsortname); + return res ? *res : NULL; +} bool valid_client(int cn) { @@ -283,6 +507,12 @@ bool buildworldstate() } } +client *getclientbysequence(int seq) +{ + loopv(clients) if(clients[i]->type != ST_EMPTY && clients[i]->clientsequence == seq) return clients[i]; + return NULL; +} + int countclients(int type, bool exclude = false) { int num = 0; @@ -318,7 +548,7 @@ int *numteamclients(int exclude = -1) int sendservermode(bool send = true) { - int sm = (autoteam ? AT_ENABLED : AT_DISABLED) | ((mastermode & MM_MASK) << 2) | (matchteamsize << 4); + int sm = (sg->autoteam ? AT_ENABLED : AT_DISABLED) | ((sg->mastermode & MM_MASK) << 2) | (sg->matchteamsize << 4); if(send) sendf(-1, 1, "ri2", SV_SERVERMODE, sm); return sm; } @@ -326,19 +556,19 @@ int sendservermode(bool send = true) void changematchteamsize(int newteamsize) { if(newteamsize < 0) return; - if(matchteamsize != newteamsize) + if(sg->matchteamsize != newteamsize) { - matchteamsize = newteamsize; + sg->matchteamsize = newteamsize; sendservermode(); } - if(mastermode == MM_MATCH && matchteamsize && m_teammode) + if(sg->mastermode == MM_MATCH && sg->matchteamsize && m_teammode) { int size[2] = { 0 }; loopv(clients) if(clients[i]->type!=ST_EMPTY && clients[i]->isauthed && clients[i]->isonrightmap) { if(team_isactive(clients[i]->team)) { - if(++size[clients[i]->team] > matchteamsize) updateclientteam(i, team_tospec(clients[i]->team), FTR_SILENTFORCE); + if(++size[clients[i]->team] > sg->matchteamsize) updateclientteam(i, team_tospec(clients[i]->team), FTR_SILENTFORCE); } } } @@ -346,18 +576,18 @@ void changematchteamsize(int newteamsize) void changemastermode(int newmode) { - if(mastermode != newmode) + if(sg->mastermode != newmode) { - mastermode = newmode; + sg->mastermode = newmode; senddisconnectedscores(-1); - if(mastermode != MM_MATCH) + if(sg->mastermode != MM_MATCH) { loopv(clients) if(clients[i]->type!=ST_EMPTY && clients[i]->isauthed) { if(clients[i]->team == TEAM_CLA_SPECT || clients[i]->team == TEAM_RVSF_SPECT) updateclientteam(i, TEAM_SPECT, FTR_SILENTFORCE); } } - else if(matchteamsize) changematchteamsize(matchteamsize); + else if(sg->matchteamsize) changematchteamsize(sg->matchteamsize); sendservermode(); } } @@ -375,7 +605,7 @@ int findcnbyaddress(ENetAddress *address) savedscore *findscore(client &c, bool insert) { if(c.type!=ST_TCPIP) return NULL; - enet_uint32 mask = ENET_HOST_TO_NET_32(mastermode == MM_MATCH ? 0xFFFF0000 : 0xFFFFFFFF); // in match mode, reconnecting from /16 subnet is allowed + enet_uint32 mask = ENET_HOST_TO_NET_32(sg->mastermode == MM_MATCH ? 0xFFFF0000 : 0xFFFFFFFF); // in match mode, reconnecting from /16 subnet is allowed if(!insert) { loopv(clients) @@ -412,11 +642,12 @@ void sendf(int cn, int chan, const char *format, ...) va_start(args, format); while(*format) switch(*format++) { - case 'x': + case 'x': // exclude client from getting this message exclude = va_arg(args, int); + ASSERT(cn == -1); break; - case 'v': + case 'v': // integer array of variable length { int n = va_arg(args, int); int *v = va_arg(args, int *); @@ -424,20 +655,37 @@ void sendf(int cn, int chan, const char *format, ...) break; } - case 'i': + case 'k': // 32 bytes (uchar) + p.put(va_arg(args, uchar *), 32); + break; + + case 'i': // one to nine integer arguments { int n = isdigit(*format) ? *format++-'0' : 1; loopi(n) putint(p, va_arg(args, int)); break; } - case 'a': + + case 'a': // one to nine mostly positive integer arguments { int n = isdigit(*format) ? *format++-'0' : 1; loopi(n) putaint(p, va_arg(args, int)); break; } - case 's': sendstring(va_arg(args, const char *), p); break; - case 'm': + + case 'u': // unsigned integer argument + putuint(p, va_arg(args, int)); + break; + + case 'I': // IPv4 address argument + putip4(p, va_arg(args, enet_uint32)); + break; + + case 's': // string + sendstring(va_arg(args, const char *), p); + break; + + case 'm': // raw message block { int n = va_arg(args, int); p.put(va_arg(args, uchar *), n); @@ -448,304 +696,327 @@ void sendf(int cn, int chan, const char *format, ...) sendpacket(cn, chan, p.finalize(), exclude); } -void sendextras() -{ - if ( gamemillis < nextsendscore ) return; - int count = 0, list[MAXCLIENTS]; - loopv(clients) - { - client &c = *clients[i]; - if ( c.type!=ST_TCPIP || !c.isauthed || !(c.md.updated && c.md.upmillis < gamemillis) ) continue; - if ( c.md.combosend ) - { - sendf(c.clientnum, 1, "ri2", SV_HUDEXTRAS, min(c.md.combo,c.md.combofrags)-1 + HE_COMBO); - c.md.combosend = false; - } - if ( c.md.dpt ) - { - list[count] = i; - count++; - } - } - nextsendscore = gamemillis + 160; // about 4 cicles - if ( !count ) return; - - packetbuf p(MAXTRANS, ENET_PACKET_FLAG_RELIABLE); - putint(p, SV_POINTS); - putint(p,count); - int *v = list; - loopi(count) - { - client &c = *clients[*v]; - putint(p,c.clientnum); putint(p,c.md.dpt); c.md.updated = false; c.md.upmillis = c.md.dpt = 0; - v++; - } - - sendpacket(-1, 1, p.finalize()); -} - void sendservmsg(const char *msg, int cn = -1) { sendf(cn, 1, "ris", SV_SERVMSG, msg); } +void sendservmsgverbose(const char *msg, int cn = -1) // channel server spam here... +{ + sendf(cn, 1, "ris", SV_SERVMSGVERB, msg); +} + void sendspawn(client *c) { if(team_isspect(c->team)) return; clientstate &gs = c->state; gs.respawn(); - gs.spawnstate(smode); + gs.spawnstate(sg->smode); gs.lifesequence++; sendf(c->clientnum, 1, "ri7vv", SV_SPAWNSTATE, gs.lifesequence, gs.health, gs.armour, gs.primary, gs.gunselect, m_arena ? c->spawnindex : -1, NUMGUNS, gs.ammo, NUMGUNS, gs.mag); - gs.lastspawn = gamemillis; + gs.lastspawn = sg->gamemillis; } -// demo -stream *demotmp = NULL, *demorecord = NULL, *demoplayback = NULL; -bool recordpackets = false; -int nextplayback = 0; +// simultaneous demo recording, fully buffered + +#define DEMORINGBUFSIZE (1<<18) // 256KB +#define MAXDEMOS 24 + +SERVPAR(demo_max_number, 5, 7, MAXDEMOS - 1, "DMaximum number of demo files in RAM"); +SERVPARLIST(demo_save, 0, 1, 0, endis, "DWrite demos to file"); +SERVSTR(demo_path, "", 0, 63, FTXT__DEMONAME, "DDemo path (and filename) prefix"); +SERVSTR(demo_filenameformat, "%w_%h_%n_%Mmin_%G", 0, 63, FTXT__FORMATSTRING, "DDemo file format string"); +SERVSTR(demo_timestampformat, "%Y%m%d_%H%M", 0, 23, FTXT__FORMATSTRING, "DDemo timestamp format string"); +SERVPARLIST(demo_debug, 0, 1, 0, endis, "DExcessive logging during demo recording (FIXME)"); + +typedef ringbuf demoringbuf; +typedef vector demobuf; +ringbuf demoringbuf_store; +ringbuf demobuf_store; + +struct demo_s +{ + volatile int sequence; + string info, mapname, filename; + vector clients_waiting, clients_served; + demoringbuf *rb; // uncomressed ringbuffer + stream *gz, *dbs; // compression handle + demobuf *db; // temp compressed file + uchar *data; // finished compressed file + int len; // bytes in data + int gmode, timeplayed, timeremain, timestamp; + uchar tiger[TIGERHASHSIZE]; // raw data checksum + void *tigerstate; + + // flags only valid for sequence > 0 + bool finish; // game ended, finish up buffer and copy to 'data' + bool error; // buffering overflow, demo lost -> double DEMORINGBUFSIZE and recompile + bool remove; // cleanup slot + bool done; // demo ready to be downloaded + bool save; // write demo to file + + demo_s() : sequence(0), rb(NULL), gz(NULL), db(NULL), data(NULL), tigerstate(NULL) {} +}; + +demo_s demos[MAXDEMOS]; + +demo_s *initdemoslot() // fetch empty demo slot, clean up surplus slots +{ + static int demosequencecounter = 0, lastdemoslot = 0; + const int demosequencewraparound = max(126, MAXDEMOS * 4); + ASSERT(ismainthread() && MAXDEMOS < 126); + for(;;) + { + int used = 0; + loopi(MAXDEMOS) if(demos[i].sequence) used++; + if(used >= demo_max_number) + { // drop old demos (oldest sequence counters first) + int oldestage = -1, oldestdemo; + loopi(MAXDEMOS) if(demos[i].sequence) + { + int age = (demosequencecounter - demos[i].sequence + demosequencewraparound) % demosequencewraparound; + if(age > oldestage) + { + oldestage = age; + oldestdemo = i; + } + } + if(oldestage >= 0) + { + demo_s &c = demos[oldestdemo]; + mlog(ACLOG_VERBOSE, "dropping demo #%d \"%s\", age %d", c.sequence, c.info, oldestage); + if(!c.done) mlog(ACLOG_ERROR, "clearing unfinished demo slot #%d, probably leaked some memory...", c.sequence); + else DELETEA(c.data); + c.rb = NULL; + c.db = NULL; + c.data = NULL; + c.tigerstate = NULL; + c.gz = c.dbs = NULL; + c.sequence = 0; + } + } + else break; + } + demo_s *d = NULL; + int n; + loopi(MAXDEMOS) if(!demos[(n = (i + lastdemoslot) % MAXDEMOS)].sequence) { d = demos + n; lastdemoslot = n + 1; break; } + ASSERT(d != NULL); + d->clients_waiting.setsize(0); + d->clients_served.setsize(0); + d->rb = demoringbuf_store.length() ? demoringbuf_store.remove() : new demoringbuf; + d->db = demobuf_store.length() ? demobuf_store.remove() : new demobuf; + DELETEA(d->data); + d->rb->clear(); + d->db->setsize(0); + d->len = 0; + d->tigerstate = tigerhash_init(d->tiger); + d->dbs = openvecfile(d->db, false); // no autodelete: we'll keep db allocated + d->gz = opengzfile(NULL, "wb", d->dbs); + d->remove = d->finish = d->error = d->done = false; + for(bool tryagain = true; tryagain; ) + { // get unique sequence number (brute force) + demosequencecounter = (d->sequence = (demosequencecounter + 1)) % demosequencewraparound; + tryagain = false; + loopi(MAXDEMOS) if(demos[i].sequence == d->sequence && demos + i != d) tryagain = true; + } + if(demo_debug) mlog(ACLOG_INFO, "initdemoslot(): use slot %d, sequence #%d", n, d->sequence); + return d; +} + +sl_semaphore sem_demoworkerthread(1, NULL); // trigger demoworkerthread + +int demoworkerthread(void *logfileprefix) // demo worker thread: compress demo data and write demo files +{ + int len; + while(1) + { + loopi(MAXDEMOS) if(demos[i].sequence) + { + demo_s &s = demos[i]; + while(s.gz && s.rb && (len = s.rb->length())) + { // compress until rb is empty + uchar *rbp = s.rb->peek(&len); + if(len != s.gz->write(rbp, len)) s.error = true; + s.rb->remove(&len); + } + if(s.finish && s.gz && s.rb && !s.rb->length()) + { // close and copy zip file + if(demo_debug) tlog(ACLOG_INFO, "demoworkerthread(): close slot %d, sequence #%d", i, s.sequence); + tigerhash_finish(s.tiger, s.tigerstate); + s.tigerstate = NULL; + string msg; + bin2hex(msg, s.tiger, TIGERHASHSIZE); + defformatstring(text)("SERVDEMOEND<%s>", msg); + ed25519_sign((uchar*)msg, NULL, (uchar*)text, strlen(text), ssk); + int stamp[3] = { -1, -1, TIGERHASHSIZE + 64 }; + lilswap(stamp, 3); + s.gz->write(stamp, sizeof(stamp)); + s.gz->write(s.tiger, TIGERHASHSIZE); + s.gz->write(msg, 64); + DELETEP(s.gz); + s.len = s.dbs->size(); + s.data = new uchar[s.len]; + s.dbs->seek(0, SEEK_SET); + s.dbs->read(s.data, s.len); + if(s.save) + { + stream *demo = openfile(s.filename, "wb"); + if(demo) + { + int wlen = (int) demo->write(s.data, s.len); + delete demo; + tlog(ACLOG_INFO, "demo #%d written to file \"%s\" (%d bytes)", s.sequence, s.filename, wlen); + } + else + { + tlog(ACLOG_INFO, "failed to write demo #%d to file \"%s\"", s.sequence, s.filename); + } + } + else tlog(ACLOG_INFO, "recording demo #%d \"%s\" (%d bytes) finished, not saved to file", s.sequence, s.filename, s.len); + s.done = true; + } + if(s.finish || s.error || s.remove) + { // cleanup demo slot + if(demo_debug) tlog(ACLOG_INFO, "demoworkerthread(): cleanup slot %d, sequence #%d", i, s.sequence); + if(s.error || s.remove) s.done = false; + DELETEP(s.gz); + DELETEP(s.dbs); + if(s.db) + { // keep the demo buffer for the next recording + if(!demobuf_store.full()) demobuf_store.add(s.db); + else delete s.db; + s.db = NULL; + } + if(s.rb) + { // keep the demo ringbuffer for the next recording + if(!demoringbuf_store.full()) demoringbuf_store.add(s.rb); + else delete s.rb; + s.rb = NULL; + } + s.finish = s.error = s.remove = false; + if(!s.done) s.sequence = 0; + } + } + sem_demoworkerthread.timedwait(500); // wait for mainloop to add some work + } + return 0; +} + +demo_s *demorecord = NULL; void writedemo(int chan, void *data, int len) { - if(!demorecord) return; - int stamp[3] = { gamemillis, chan, len }; - lilswap(stamp, 3); - demorecord->write(stamp, sizeof(stamp)); - demorecord->write(data, len); + if(demorecord && !demorecord->sequence) demorecord = NULL; + if(!demorecord || !demorecord->rb) return; + int stamp[3] = { sg->gamemillis, chan, len }; + if(demorecord->rb->maxsize() - demorecord->rb->length() > int(sizeof(stamp)) + len) + { + lilswap(stamp, 3); + demorecord->rb->add((uchar *)stamp, sizeof(stamp)); + tigerhash_add(demorecord->tiger, (uchar *)stamp, sizeof(stamp), demorecord->tigerstate); + demorecord->rb->add((uchar *)data, len); + tigerhash_add(demorecord->tiger, (uchar *)data, len, demorecord->tigerstate); + sem_demoworkerthread.post(); + } + else + { + if(!demorecord->error) mlog(ACLOG_ERROR, "demorecord ringbuffer overflow -> discarding demo #%d", demorecord->sequence); + demorecord->error = true; // ringbuffer-overflow: discard demo + } } void recordpacket(int chan, void *data, int len) { - if(recordpackets) writedemo(chan, data, len); + if(sg->recordpackets) writedemo(chan, data, len); } void recordpacket(int chan, ENetPacket *packet) { - if(recordpackets) writedemo(chan, packet->data, (int)packet->dataLength); + if(sg->recordpackets) writedemo(chan, packet->data, (int)packet->dataLength); } -#ifdef STANDALONE -const char *currentserver(int i) -{ - static string curSRVinfo; - string r; - r[0] = '\0'; - switch(i) - { - case 1: { copystring(r, scl.ip[0] ? scl.ip : "local"); break; } // IP - case 2: { copystring(r, scl.logident[0] ? scl.logident : "local"); break; } // HOST - case 3: { formatstring(r)("%d", scl.serverport); break; } // PORT - // the following are used by a client, a server will simply return empty strings for them - case 4: - case 5: - case 6: - case 7: - case 8: - { - break; - } - default: - { - formatstring(r)("%s %d", scl.ip[0] ? scl.ip : "local", scl.serverport); - break; - } - } - copystring(curSRVinfo, r); - return curSRVinfo; +void demorecord_beginintermission() +{ // send hash to clients to sign and restart demo hash + if(!demorecord) return; + + uchar interm_tiger[TIGERHASHSIZE]; + tigerhash_finish(demorecord->tiger, demorecord->tigerstate); + memcpy(interm_tiger, demorecord->tiger, TIGERHASHSIZE); + demorecord->tigerstate = tigerhash_init(demorecord->tiger); // start again to hash the rest of the demo + + if(demo_debug) mlog(ACLOG_INFO, "demorecord_beginintermission(): sequence #%d", demorecord->sequence); + sendf(-1, 1, "rim", SV_DEMOCHECKSUM, TIGERHASHSIZE, interm_tiger); } -#endif - -// these are actually the values used by the client, the server ones are in "scl". -string demofilenameformat = DEFDEMOFILEFMT; -string demotimestampformat = DEFDEMOTIMEFMT; -int demotimelocal = 0; - -#ifdef STANDALONE -#define DEMOFORMAT scl.demofilenameformat -#define DEMOTSFORMAT scl.demotimestampformat -#else -#define DEMOFORMAT demofilenameformat -#define DEMOTSFORMAT demotimestampformat -#endif - -const char *getDemoFilename(int gmode, int mplay, int mdrop, int tstamp, char *srvmap) -{ - // we use the following internal mapping of formatchars: - // %g : gamemode (int) %G : gamemode (chr) %F : gamemode (full) - // %m : minutes remaining %M : minutes played - // %s : seconds remaining %S : seconds played - // %h : IP of server %H : hostname of server - // %n : mapName - // %w : timestamp "when" - static string dmofn; - copystring(dmofn, ""); - - int cc = 0; - int mc = strlen(DEMOFORMAT); - - while(ccrecordpackets = false; - delete demorecord; - recordpackets = false; + if(demo_debug) mlog(ACLOG_INFO, "enddemorecord(): sequence #%d", demorecord->sequence); + if(sg->gamemillis < DEMO_MINTIME) + { + mlog(ACLOG_INFO, "Demo #%d discarded.", demorecord->sequence); + demorecord->remove = true; + } + else + { // finish recording, assemble game data, + int len = demorecord->dbs ? demorecord->dbs->size() : 0; // (still a few bytes missing, sue me) + int mr = sg->gamemillis >= sg->gamelimit ? 0 : (sg->gamelimit - sg->gamemillis + 60000 - 1)/60000; + formatstring(demorecord->info)("%s: %s, %s", asctimestr(), modestr(gamemode), sg->smapname); + if(mr) concatformatstring(demorecord->info, ", %d mr", mr); + defformatstring(info)("%s, %.2fMB", demorecord->info, (len + 10000) / (1024*1024.f)); + defformatstring(msg)("Demo #%d \"%s\" recorded\nPress F10 to download it from the server..", demorecord->sequence, info); + sendservmsgverbose(msg); + mlog(ACLOG_INFO, "Demo \"%s\" recorded.", info); + + copystring(demorecord->mapname, behindpath(sg->smapname)); + demorecord->gmode = gamemode; + demorecord->timeplayed = sg->gamemillis >= sg->gamelimit ? sg->gamelimit/1000 : sg->gamemillis/1000; + demorecord->timeremain = sg->gamemillis >= sg->gamelimit ? 0 : (sg->gamelimit - sg->gamemillis)/1000; + demorecord->timestamp = int(time(NULL) / 60); + demorecord->save = demo_save != 0; + string buf; + formatstring(demorecord->filename)("%s%s.dmo", demo_path, formatdemofilename(demo_filenameformat, demo_timestampformat, demorecord->mapname, gamemode, demorecord->timestamp, demorecord->timeplayed, demorecord->timeremain, servownip, buf)); + path(demorecord->filename); + + sg->demosequence = demorecord->sequence; + demorecord->finish = true; + } demorecord = NULL; - - if(!demotmp) return; - - if(gamemillis < DEMO_MINTIME) - { - delete demotmp; - demotmp = NULL; - logline(ACLOG_INFO, "Demo discarded."); - return; - } - - int len = demotmp->size(); - demotmp->seek(0, SEEK_SET); - if(demofiles.length() >= scl.maxdemos) - { - delete[] demofiles[0].data; - demofiles.remove(0); - } - int mr = gamemillis >= gamelimit ? 0 : (gamelimit - gamemillis + 60000 - 1)/60000; - demofile &d = demofiles.add(); - - //2010oct10:ft: suggests : formatstring(d.info)("%s, %s, %.2f%s", modestr(gamemode), smapname, len > 1024*1024 ? len/(1024*1024.f) : len/1024.0f, len > 1024*1024 ? "MB" : "kB"); // the datetime bit is pretty useless in the servmesg, no?! - formatstring(d.info)("%s: %s, %s, %.2f%s", asctimestr(), modestr(gamemode), smapname, len > 1024*1024 ? len/(1024*1024.f) : len/1024.0f, len > 1024*1024 ? "MB" : "kB"); - if(mr) { concatformatstring(d.info, ", %d mr", mr); concatformatstring(d.file, "_%dmr", mr); } - defformatstring(msg)("Demo \"%s\" recorded\nPress F10 to download it from the server..", d.info); - sendservmsg(msg); - logline(ACLOG_INFO, "Demo \"%s\" recorded.", d.info); - - // 2011feb05:ft: previously these two static formatstrings were used .. - //formatstring(d.file)("%s_%s_%s", timestring(), behindpath(smapname), modestr(gamemode, true)); // 20100522_10.08.48_ac_mines_DM.dmo - //formatstring(d.file)("%s_%s_%s", modestr(gamemode, true), behindpath(smapname), timestring( true, "%Y.%m.%d_%H%M")); // DM_ac_mines.2010.05.22_1008.dmo - // .. now we use client-side parseable fileattribs - int mPLAY = gamemillis >= gamelimit ? gamelimit/1000 : gamemillis/1000; - int mDROP = gamemillis >= gamelimit ? 0 : (gamelimit - gamemillis)/1000; - int iTIME = time(NULL); - const char *mTIME = numtime(); - const char *sMAPN = behindpath(smapname); - string iMAPN; - copystring(iMAPN, sMAPN); - formatstring(d.file)( "%d:%d:%d:%s:%s", gamemode, mPLAY, mDROP, mTIME, iMAPN); - - d.data = new uchar[len]; - d.len = len; - demotmp->read(d.data, len); - delete demotmp; - demotmp = NULL; - if(scl.demopath[0]) - { - formatstring(msg)("%s%s.dmo", scl.demopath, getDemoFilename(gamemode, mPLAY, mDROP, iTIME, iMAPN)); //d.file); - path(msg); - stream *demo = openfile(msg, "wb"); - if(demo) - { - int wlen = (int) demo->write(d.data, d.len); - delete demo; - logline(ACLOG_INFO, "demo written to file \"%s\" (%d bytes)", msg, wlen); - } - else - { - logline(ACLOG_INFO, "failed to write demo to file \"%s\"", msg); - } - } + sem_demoworkerthread.post(); } void setupdemorecord() { if(numlocalclients() || !m_mp(gamemode) || gamemode == GMODE_COOPEDIT) return; - defformatstring(demotmppath)("demos/demorecord_%s_%d", scl.ip[0] ? scl.ip : "local", scl.serverport); - demotmp = opentempfile(demotmppath, "w+b"); - if(!demotmp) return; + ASSERT(demorecord == NULL); - stream *f = opengzfile(NULL, "wb", demotmp); - if(!f) - { - delete demotmp; - demotmp = NULL; - return; - } + demorecord = initdemoslot(); + if(!sg->demosequence) sg->demosequence = demorecord->sequence; + formatstring(demorecord->info)("%s %s", modestr(gamemode), sg->smapname); - sendservmsg("recording demo"); - logline(ACLOG_INFO, "Demo recording started."); + sendservmsgverbose("recording demo"); + mlog(ACLOG_INFO, "Demo recording started (#%d).", demorecord->sequence); - demorecord = f; - recordpackets = false; + sg->recordpackets = false; demoheader hdr; + memset(&hdr, 0, sizeof(demoheader)); memcpy(hdr.magic, DEMO_MAGIC, sizeof(hdr.magic)); hdr.version = DEMO_VERSION; hdr.protocol = SERVER_PROTOCOL_VERSION; lilswap(&hdr.version, 1); lilswap(&hdr.protocol, 1); - memset(hdr.desc, 0, DHDR_DESCCHARS); - defformatstring(desc)("%s, %s, %s %s", modestr(gamemode, false), behindpath(smapname), asctimestr(), servdesc_current); + defformatstring(desc)("%s, %s, %s %s", modestr(gamemode, false), behindpath(sg->smapname), asctimestr(), sg->servdesc_current); if(strlen(desc) > DHDR_DESCCHARS) - formatstring(desc)("%s, %s, %s %s", modestr(gamemode, true), behindpath(smapname), asctimestr(), servdesc_current); + formatstring(desc)("%s, %s, %s %s", modestr(gamemode, true), behindpath(sg->smapname), asctimestr(), sg->servdesc_current); desc[DHDR_DESCCHARS - 1] = '\0'; strcpy(hdr.desc, desc); - memset(hdr.plist, 0, DHDR_PLISTCHARS); const char *bl = ""; loopv(clients) { @@ -754,19 +1025,37 @@ void setupdemorecord() if(strlen(hdr.plist) + strlen(ci->name) < DHDR_PLISTCHARS - 2) { strcat(hdr.plist, bl); strcat(hdr.plist, ci->name); } bl = " "; } - demorecord->write(&hdr, sizeof(demoheader)); + tigerhash_add(demorecord->tiger, (uchar *)&hdr, sizeof(demoheader), demorecord->tigerstate); + demorecord->rb->add((uchar *)&hdr, sizeof(demoheader)); packetbuf p(MAXTRANS, ENET_PACKET_FLAG_RELIABLE); welcomepacket(p, -1); writedemo(1, p.buf, p.len); + if(demo_debug) mlog(ACLOG_INFO, "setupdemorecord(): sequence #%d, %s on %s", demorecord->sequence, modestr(gamemode, false), behindpath(sg->smapname)); } +int donedemosort(demo_s **a, demo_s **b) { return (*a)->timestamp - (*b)->timestamp; } + void listdemos(int cn) { packetbuf p(MAXTRANS, ENET_PACKET_FLAG_RELIABLE); putint(p, SV_SENDDEMOLIST); - putint(p, demofiles.length()); - loopv(demofiles) sendstring(demofiles[i].info, p); + vector donedemos; + loopi(MAXDEMOS) if(demos[i].sequence && demos[i].done) donedemos.add(demos + i); + putint(p, donedemos.length()); + donedemos.sort(donedemosort); + loopv(donedemos) + { + demo_s &d = *donedemos[i]; + putint(p, d.sequence); + sendstring(d.info, p); + putint(p, d.len); + sendstring(d.mapname, p); + putint(p, d.gmode); + putint(p, d.timeplayed); + putint(p, d.timeremain); + putint(p, d.timestamp); + } sendpacket(cn, 1, p.finalize()); } @@ -774,62 +1063,99 @@ static void cleardemos(int n) { if(!n) { - loopv(demofiles) delete[] demofiles[i].data; - demofiles.shrink(0); + loopi(MAXDEMOS) if(demos[i].done) demos[i].remove = true; sendservmsg("cleared all demos"); } - else if(demofiles.inrange(n-1)) + else loopi(MAXDEMOS) if(demos[i].sequence == n && demos[i].done) { - delete[] demofiles[n-1].data; - demofiles.remove(n-1); - defformatstring(msg)("cleared demo %d", n); + demos[i].remove = true; + defformatstring(msg)("cleared demo #%d", n); sendservmsg(msg); } } -bool sending_demo = false; - void senddemo(int cn, int num) { client *cl = cn>=0 ? clients[cn] : NULL; - bool is_admin = (cl && cl->role == CR_ADMIN); - if(scl.demo_interm && (!interm || totalclients > 2) && !is_admin) + if(num < 1) num = sg->demosequence; + demo_s *d = NULL; + loopi(MAXDEMOS) if(demos[i].sequence == num) d = demos + i; + if(d) { - sendservmsg("\f3sorry, but this server only sends demos at intermission.\n wait for the end of this game, please", cn); - return; - } - if(!num) num = demofiles.length(); - if(!demofiles.inrange(num-1)) - { - if(demofiles.empty()) sendservmsg("no demos available", cn); - else + bool alreadywaiting = false, alreadyserved = false; + loopv(d->clients_waiting) if(d->clients_waiting[i] == cl->clientsequence) alreadywaiting = true; + loopv(d->clients_served) if(d->clients_served[i] == cl->clientsequence) alreadyserved = true; + if(alreadyserved) { - defformatstring(msg)("no demo %d available", num); - sendservmsg(msg, cn); + sendservmsg("\f3Sorry, you have already downloaded this demo.", cl->clientnum); + } + else if(!alreadywaiting) + { + d->clients_waiting.add(cl->clientsequence); + if(!d->done) + { + defformatstring(msg)("scheduled download of demo #%d \"%s\"\n...download will begin, once the demo is finished recording", num, d->info); + sendservmsg(msg, cn); + } } - return; } - demofile &d = demofiles[num-1]; - loopv(d.clientssent) if(d.clientssent[i].ip == cl->peer->address.host && d.clientssent[i].clientnum == cl->clientnum) + else { - sendservmsg("\f3Sorry, you have already downloaded this demo.", cl->clientnum); - return; + defformatstring(msg)("demo #%d not available", num); + sendservmsg(msg, cn); } - clientidentity &ci = d.clientssent.add(); - ci.ip = cl->peer->address.host; - ci.clientnum = cl->clientnum; +} - if (interm) sending_demo = true; - packetbuf p(MAXTRANS + d.len, ENET_PACKET_FLAG_RELIABLE); - putint(p, SV_SENDDEMO); - sendstring(d.file, p); - putint(p, d.len); - p.put(d.data, d.len); - sendpacket(cn, 2, p.finalize()); +void checkdemotransmissions() +{ + static int dpointer = 0, curcl = -1, curclseq = 0, curdemoseq = 0;//, curdemosent = 0; + if(curcl >= 0) + { // demo transmission in progress + client *cl = clients[curcl]; + demo_s *d = NULL; + loopi(MAXDEMOS) if(demos[i].sequence == curdemoseq && demos[i].done) d = demos + i; + if(d && cl->clientsequence == curclseq) + { // demo and client still exist: continue transmission + + // FIXME.... + packetbuf p(MAXTRANS + d->len, ENET_PACKET_FLAG_RELIABLE); + putint(p, SV_SENDDEMO); + putint(p, d->sequence); + sendstring(d->mapname, p); + putint(p, d->gmode); + putint(p, d->timeplayed); + putint(p, d->timeremain); + putint(p, d->timestamp); + putint(p, d->len); + p.put(d->data, d->len); + sendpacket(cl->clientnum, 2, p.finalize()); + + } + curcl = -1; // done + } + else + { + demo_s *d = demos + dpointer; + if(d->sequence && d->done && d->clients_waiting.length()) + { + client *cl = getclientbysequence(d->clients_waiting[0]); + if(cl) + { + d->clients_served.add(d->clients_waiting.remove(0)); + curcl = cl->clientnum; + curclseq = cl->clientsequence; + curdemoseq = d->sequence; + //curdemosent = 0; + } + } + else dpointer = (dpointer + 1) % MAXDEMOS; + } } int demoprotocol; bool watchingdemo = false; +stream *demoplayback = NULL; +int nextplayback = 0; void enddemoplayback() { @@ -850,7 +1176,7 @@ void setupdemoplayback() demoheader hdr; string msg; msg[0] = '\0'; - defformatstring(file)("demos/%s.dmo", smapname); + defformatstring(file)("demos/%s.dmo", sg->smapname); path(file); demoplayback = opengzfile(file, "rb"); if(!demoplayback) formatstring(msg)("could not read demo \"%s\"", file); @@ -861,8 +1187,7 @@ void setupdemoplayback() lilswap(&hdr.version, 1); lilswap(&hdr.protocol, 1); if(hdr.version!=DEMO_VERSION) formatstring(msg)("demo \"%s\" requires an %s version of AssaultCube", file, hdr.versionsmapname, -1); watchingdemo = true; if(demoplayback->read(&nextplayback, sizeof(nextplayback))!=sizeof(nextplayback)) @@ -888,7 +1213,7 @@ void setupdemoplayback() void readdemo() { if(!demoplayback) return; - while(gamemillis>=nextplayback) + while(sg->gamemillis >= nextplayback) { int chan, len; if(demoplayback->read(&chan, sizeof(chan))!=sizeof(chan) || @@ -917,21 +1242,9 @@ void readdemo() } } -struct sflaginfo -{ - int state; - int actor_cn; - float pos[3]; - int lastupdate; - int stolentime; - short x, y; // flag entity location - - sflaginfo() { actor_cn = -1; } -} sflaginfos[2]; - void putflaginfo(packetbuf &p, int flag) { - sflaginfo &f = sflaginfos[flag]; + sflaginfo &f = sg->sflaginfos[flag]; putint(p, SV_FLAGINFO); putint(p, flag); putint(p, f.state); @@ -949,7 +1262,7 @@ void putflaginfo(packetbuf &p, int flag) inline void send_item_list(packetbuf &p) { putint(p, SV_ITEMLIST); - loopv(sents) if(sents[i].spawned) putint(p, i); + loopv(sg->sents) if(sg->sents[i].spawned) putint(p, i); putint(p, -1); if(m_flags) loopi(2) putflaginfo(p, i); } @@ -977,8 +1290,8 @@ bool flagdistance(sflaginfo &f, int cn) if(pdist) { c.farpickups++; - logline(ACLOG_INFO, "[%s] %s %s the %s flag at distance %.2f (%d)", - c.hostname, c.name, (pdist==2?"tried to touch":"touched"), team_string(&f == sflaginfos + 1), dist, c.farpickups); + mlog(ACLOG_INFO, "[%s] %s %s the %s flag at distance %.2f (%d)", + c.hostname, c.name, (pdist==2?"tried to touch":"touched"), team_string(&f == sg->sflaginfos + 1), dist, c.farpickups); if (pdist==2) return false; } return lagging ? false : true; // today I found a lag hacker :: Brahma, 19-oct-2010... lets test it a bit @@ -995,7 +1308,7 @@ void sendflaginfo(int flag = -1, int cn = -1) void flagmessage(int flag, int message, int actor, int cn = -1) { if(message == FM_KTFSCORE) - sendf(cn, 1, "riiiii", SV_FLAGMSG, flag, message, actor, (gamemillis - sflaginfos[flag].stolentime) / 1000); + sendf(cn, 1, "riiiii", SV_FLAGMSG, flag, message, actor, (sg->gamemillis - sg->sflaginfos[flag].stolentime) / 1000); else sendf(cn, 1, "riiii", SV_FLAGMSG, flag, message, actor); } @@ -1003,8 +1316,8 @@ void flagmessage(int flag, int message, int actor, int cn = -1) void flagaction(int flag, int action, int actor) { if(!valid_flag(flag)) return; - sflaginfo &f = sflaginfos[flag]; - sflaginfo &of = sflaginfos[team_opposite(flag)]; + sflaginfo &f = sg->sflaginfos[flag]; + sflaginfo &of = sg->sflaginfos[team_opposite(flag)]; bool deadactor = valid_client(actor) ? clients[actor]->state.state != CS_ALIVE || team_isspect(clients[actor]->team): true; int abort = 0; int score = 0; @@ -1070,7 +1383,7 @@ void flagaction(int flag, int action, int actor) if(deadactor || f.state != CTFF_INBASE || !flagdistance(f, actor)) { abort = 20; break; } f.state = CTFF_STOLEN; f.actor_cn = actor; - f.stolentime = gamemillis; + f.stolentime = sg->gamemillis; message = FM_PICKUP; break; case FA_SCORE: // f = carried by actor flag @@ -1100,7 +1413,7 @@ void flagaction(int flag, int action, int actor) } if(abort) { - logline(ACLOG_DEBUG,"aborting flagaction(flag %d, action %d, actor %d), reason %d, resending flag states", flag, action, actor, abort); // FIXME: remove this logline after some time - it will only show a few bad ping effects + mlog(ACLOG_DEBUG,"aborting flagaction(flag %d, action %d, actor %d), reason %d, resending flag states", flag, action, actor, abort); sendflaginfo(); return; } @@ -1109,7 +1422,6 @@ void flagaction(int flag, int action, int actor) client *c = clients[actor]; c->state.flagscore += score; sendf(-1, 1, "riii", SV_FLAGCNT, actor, c->state.flagscore); - if (m_teammode) computeteamwork(c->team, c->clientnum); /** WIP */ } if(valid_client(actor)) { @@ -1117,47 +1429,46 @@ void flagaction(int flag, int action, int actor) switch(message) { case FM_PICKUP: - logline(ACLOG_INFO,"[%s] %s %s the flag", c.hostname, c.name, action == FA_STEAL ? "stole" : "picked up"); + mlog(ACLOG_INFO,"[%s] %s %s the flag", c.hostname, c.name, action == FA_STEAL ? "stole" : "picked up"); break; case FM_DROP: case FM_LOST: - logline(ACLOG_INFO,"[%s] %s %s the flag", c.hostname, c.name, message == FM_LOST ? "lost" : "dropped"); + mlog(ACLOG_INFO,"[%s] %s %s the flag", c.hostname, c.name, message == FM_LOST ? "lost" : "dropped"); break; case FM_RETURN: - logline(ACLOG_INFO,"[%s] %s returned the flag", c.hostname, c.name); + mlog(ACLOG_INFO,"[%s] %s returned the flag", c.hostname, c.name); break; case FM_SCORE: if(m_htf) - logline(ACLOG_INFO, "[%s] %s hunted the flag for %s, new score %d", c.hostname, c.name, team_string(c.team), c.state.flagscore); + mlog(ACLOG_INFO, "[%s] %s hunted the flag for %s, new score %d", c.hostname, c.name, team_string(c.team), c.state.flagscore); else - logline(ACLOG_INFO, "[%s] %s scored with the flag for %s, new score %d", c.hostname, c.name, team_string(c.team), c.state.flagscore); + mlog(ACLOG_INFO, "[%s] %s scored with the flag for %s, new score %d", c.hostname, c.name, team_string(c.team), c.state.flagscore); break; case FM_KTFSCORE: - logline(ACLOG_INFO, "[%s] %s scored, carrying for %d seconds, new score %d", c.hostname, c.name, (gamemillis - f.stolentime) / 1000, c.state.flagscore); + mlog(ACLOG_INFO, "[%s] %s scored, carrying for %d seconds, new score %d", c.hostname, c.name, (sg->gamemillis - f.stolentime) / 1000, c.state.flagscore); break; case FM_SCOREFAIL: - logline(ACLOG_INFO, "[%s] %s failed to score", c.hostname, c.name); + mlog(ACLOG_INFO, "[%s] %s failed to score", c.hostname, c.name); break; default: - logline(ACLOG_INFO, "flagaction %d, actor %d, flag %d, message %d", action, actor, flag, message); + mlog(ACLOG_INFO, "flagaction %d, actor %d, flag %d, message %d", action, actor, flag, message); break; } - flagpoints (&c, message); } else { switch(message) { case FM_RESET: - logline(ACLOG_INFO,"the server reset the flag for team %s", team_string(flag)); + mlog(ACLOG_INFO,"the server reset the flag for team %s", team_string(flag)); break; default: - logline(ACLOG_INFO, "flagaction %d with invalid actor cn %d, flag %d, message %d", action, actor, flag, message); + mlog(ACLOG_INFO, "flagaction %d with invalid actor cn %d, flag %d, message %d", action, actor, flag, message); break; } } - f.lastupdate = gamemillis; + f.lastupdate = sg->gamemillis; sendflaginfo(flag); if(message >= 0) flagmessage(flag, message, valid_client(actor) ? actor : -1); @@ -1167,7 +1478,7 @@ int clienthasflag(int cn) { if(m_flags && valid_client(cn)) { - loopi(2) { if(sflaginfos[i].state==CTFF_STOLEN && sflaginfos[i].actor_cn==cn) return i; } + loopi(2) { if(sg->sflaginfos[i].state==CTFF_STOLEN && sg->sflaginfos[i].actor_cn==cn) return i; } } return -1; } @@ -1177,9 +1488,9 @@ void ctfreset() int idleflag = m_ktf ? rnd(2) : -1; loopi(2) { - sflaginfos[i].actor_cn = -1; - sflaginfos[i].state = i == idleflag ? CTFF_IDLE : CTFF_INBASE; - sflaginfos[i].lastupdate = -1; + sg->sflaginfos[i].actor_cn = -1; + sg->sflaginfos[i].state = i == idleflag ? CTFF_IDLE : CTFF_INBASE; + sg->sflaginfos[i].lastupdate = -1; } } @@ -1197,7 +1508,7 @@ void resetflag(int cn) void htf_forceflag(int flag) { - sflaginfo &f = sflaginfos[flag]; + sflaginfo &f = sg->sflaginfos[flag]; int besthealth = 0; vector clientnumbers; loopv(clients) if(clients[i]->type!=ST_EMPTY) @@ -1226,20 +1537,19 @@ void htf_forceflag(int flag) f.actor_cn = cl->clientnum; sendflaginfo(flag); flagmessage(flag, FM_PICKUP, cl->clientnum); - logline(ACLOG_INFO,"[%s] %s got forced to pickup the flag", cl->hostname, cl->name); + mlog(ACLOG_INFO,"[%s] %s got forced to pickup the flag", cl->hostname, cl->name); } - f.lastupdate = gamemillis; + f.lastupdate = sg->gamemillis; } -int arenaround = 0, arenaroundstartmillis = 0; - int cmpscore(const int *a, const int *b) { return clients[*a]->at3_score - clients[*b]->at3_score; } vector tdistrib; vector sdistrib; void distributeteam(int team) { - int numsp = team == 100 ? smapstats.spawns[2] : smapstats.spawns[team]; + int numsp = 0; + if(sg->curmap) numsp = team == 100 ? sg->curmap->entstats.spawns[2] : sg->curmap->entstats.spawns[team & 1]; if(!numsp) numsp = 30; // no spawns: try to distribute anyway twoint ti; tdistrib.shrink(0); @@ -1285,24 +1595,16 @@ void distributespawns() } } -bool items_blocked = false; -bool free_items(int n) -{ - client *c = clients[n]; - int waitspawn = min(c->ping,200) + c->state.spawn; // flowtron to Brahma: can't this be removed now? (re: rev. 5270) - return !items_blocked && (waitspawn < gamemillis); -} - void checkitemspawns(int); void arenacheck() { - if(!m_arena || interm || gamemillisinterm || sg->gamemillis < sg->arenaround || !numactiveclients()) return; - if(arenaround) + if(sg->arenaround) { // start new arena round - arenaround = 0; - arenaroundstartmillis = gamemillis; + sg->arenaround = 0; + sg->arenaroundstartmillis = sg->gamemillis; distributespawns(); checkitemspawns(60*1000); // the server will respawn all items now loopv(clients) if(clients[i]->type!=ST_EMPTY && clients[i]->isauthed) @@ -1310,7 +1612,6 @@ void arenacheck() if(clients[i]->isonrightmap && team_isactive(clients[i]->team)) sendspawn(clients[i]); } - items_blocked = false; return; } @@ -1332,7 +1633,7 @@ void arenacheck() if(enemies && (!alive_enemies || player1->state == CS_DEAD)) { sendf(-1, 1, "ri2", SV_ARENAWIN, m_teammode ? (alive ? alive->clientnum : -1) : (alive && alive->type == ENT_BOT ? -2 : player1->state == CS_ALIVE ? player1->clientnum : -1)); - arenaround = gamemillis+5000; + sg->arenaround = sg->gamemillis + 5000; } return; } @@ -1356,16 +1657,15 @@ void arenacheck() } } - if(autoteam && m_teammode && mastermode != MM_MATCH) + if(sg->autoteam && m_teammode && sg->mastermode != MM_MATCH) { int *ntc = numteamclients(); if((!ntc[0] || !ntc[1]) && (ntc[0] > 1 || ntc[1] > 1)) refillteams(true, FTR_AUTOTEAM); } - if(!dead || gamemillis < lastdeath + 500) return; - items_blocked = true; + if(!dead || sg->gamemillis < lastdeath + 500) return; sendf(-1, 1, "ri2", SV_ARENAWIN, alive ? alive->clientnum : -1); - arenaround = gamemillis+5000; - if(autoteam && m_teammode) refillteams(true); + sg->arenaround = sg->gamemillis + 5000; + if(sg->autoteam && m_teammode) refillteams(true); } #define SPAMREPEATINTERVAL 20 // detect doubled lines only if interval < 20 seconds @@ -1432,12 +1732,13 @@ void sendteamtext(char *text, int sender, int msgtype) ENetPacket *packet = p.finalize(); recordpacket(1, packet); int &st = clients[sender]->team; - loopv(clients) if(i!=sender) + loopv(clients) { int &rt = clients[i]->team; if((rt == TEAM_SPECT && clients[i]->role == CR_ADMIN) || // spect-admin reads all (team_isactive(st) && st == team_group(rt)) || // player to own team + own spects - (team_isspect(st) && team_isspect(rt))) // spectator to other spectators + (team_isspect(st) && team_isspect(rt)) || // spectator to other spectators + (i == sender)) // "local" echo sendpacket(i, 1, packet); } } @@ -1498,15 +1799,15 @@ int spawntime(int type) bool serverpickup(int i, int sender) // server side item pickup, acknowledge first client that gets it { const char *hn = sender >= 0 && clients[sender]->type == ST_TCPIP ? clients[sender]->hostname : NULL; - if(!sents.inrange(i)) + if(!sg->sents.inrange(i)) { - if(hn && !m_coop) logline(ACLOG_INFO, "[%s] tried to pick up entity #%d - doesn't exist on this map", hn, i); + if(hn && !m_coop) mlog(ACLOG_INFO, "[%s] tried to pick up entity #%d - doesn't exist on this map", hn, i); return false; } - server_entity &e = sents[i]; + server_entity &e = sg->sents[i]; if(!e.spawned) { - if(!e.legalpickup && hn && !m_demo) logline(ACLOG_INFO, "[%s] tried to pick up entity #%d (%s) - can't be picked up in this gamemode or at all", hn, i, entnames[e.type]); + if(!e.legalpickup && hn && !m_demo) mlog(ACLOG_INFO, "[%s] tried to pick up entity #%d (%s) - can't be picked up in this gamemode or at all", hn, i, entnames[e.type]); return false; } if(sender>=0) @@ -1514,21 +1815,21 @@ bool serverpickup(int i, int sender) // server side item pickup, acknowl client *cl = clients[sender]; if(cl->type==ST_TCPIP) { - if( cl->state.state!=CS_ALIVE || !cl->state.canpickup(e.type) || ( m_arena && !free_items(sender) ) ) return false; + if(cl->state.state != CS_ALIVE || !cl->state.canpickup(e.type)) return false; vec v(e.x, e.y, cl->state.o.z); float dist = cl->state.o.dist(v); int pdist = check_pdist(cl,dist); if (pdist) { cl->farpickups++; - if (!m_demo) logline(ACLOG_INFO, "[%s] %s %s up entity #%d (%s), distance %.2f (%d)", + if (!m_demo) mlog(ACLOG_INFO, "[%s] %s %s up entity #%d (%s), distance %.2f (%d)", cl->hostname, cl->name, (pdist==2?"tried to pick":"picked"), i, entnames[e.type], dist, cl->farpickups); if (pdist==2) return false; } } sendf(-1, 1, "ri3", SV_ITEMACC, i, sender); - cl->state.pickup(sents[i].type); - if (m_lss && sents[i].type == I_GRENADE) cl->state.pickup(sents[i].type); // get two nades at lss + cl->state.pickup(sg->sents[i].type); + if (m_lss && sg->sents[i].type == I_GRENADE) cl->state.pickup(sg->sents[i].type); // get two nades at lss } e.spawned = false; if(!m_lms) e.spawntime = spawntime(e.type); @@ -1538,13 +1839,13 @@ bool serverpickup(int i, int sender) // server side item pickup, acknowl void checkitemspawns(int diff) { if(!diff) return; - loopv(sents) if(sents[i].spawntime) + loopv(sg->sents) if(sg->sents[i].spawntime) { - sents[i].spawntime -= diff; - if(sents[i].spawntime<=0) + sg->sents[i].spawntime -= diff; + if(sg->sents[i].spawntime<=0) { - sents[i].spawntime = 0; - sents[i].spawned = true; + sg->sents[i].spawntime = 0; + sg->sents[i].spawned = true; sendf(-1, 1, "ri2", SV_ITEMSPAWN, i); } } @@ -1552,8 +1853,7 @@ void checkitemspawns(int diff) void serverdamage(client *target, client *actor, int damage, int gun, bool gib, const vec &hitpush = vec(0, 0, 0)) { - if (!m_demo && !m_coop && !validdamage(target, actor, damage, gun, gib)) return; - if ( m_arena && gun == GUN_GRENADE && arenaroundstartmillis + 2000 > gamemillis && target != actor ) return; + if ( m_arena && gun == GUN_GRENADE && sg->arenaroundstartmillis + 2000 > sg->gamemillis && target != actor ) return; clientstate &ts = target->state; ts.dodamage(damage, gun); if(damage < INT_MAX) @@ -1562,7 +1862,6 @@ void serverdamage(client *target, client *actor, int damage, int gun, bool gib, sendf(-1, 1, "ri7", gib ? SV_GIBDAMAGE : SV_DAMAGE, target->clientnum, actor->clientnum, gun, damage, ts.armour, ts.health); if(target!=actor) { - checkcombo (target, actor, damage, gun); if(!hitpush.iszero()) { vec v(hitpush); @@ -1577,7 +1876,6 @@ void serverdamage(client *target, client *actor, int damage, int gun, bool gib, int targethasflag = clienthasflag(target->clientnum); bool tk = false, suic = false; target->state.deaths++; - checkfrag(target, actor, gun, gib); if(target!=actor) { if(!isteam(target->team, actor->team)) actor->state.frags += gib && gun != GUN_GRENADE && gun != GUN_SHOTGUN ? 2 : 1; @@ -1592,7 +1890,7 @@ void serverdamage(client *target, client *actor, int damage, int gun, bool gib, { // suicide actor->state.frags--; suic = true; - logline(ACLOG_INFO, "[%s] %s suicided", actor->hostname, actor->name); + mlog(ACLOG_INFO, "[%s] %s suicided", actor->hostname, actor->name); } sendf(-1, 1, "ri5", gib ? SV_GIBDIED : SV_DIED, target->clientnum, actor->clientnum, actor->state.frags, gun); if((suic || tk) && (m_htf || m_ktf) && targethasflag >= 0) @@ -1602,8 +1900,8 @@ void serverdamage(client *target, client *actor, int damage, int gun, bool gib, } target->position.setsize(0); ts.state = CS_DEAD; - ts.lastdeath = gamemillis; - if(!suic) logline(ACLOG_INFO, "[%s] %s %s%s %s", actor->hostname, actor->name, valid_weapon(gun) ? killmessages[gib ? 1 : 0][gun] : "smurfed", tk ? " their teammate" : "", target->name); + ts.lastdeath = sg->gamemillis; + if(!suic) mlog(ACLOG_INFO, "[%s] %s %s%s %s", actor->hostname, actor->name, valid_weapon(gun) ? killmessages[gib ? 1 : 0][gun] : "smurfed", tk ? " their teammate" : "", target->name); if(m_flags && targethasflag >= 0) { if(m_ctf) @@ -1621,7 +1919,7 @@ void serverdamage(client *target, client *actor, int damage, int gun, bool gib, if( actor->state.frags < scl.banthreshold || /** teamkilling more than 6 (defaults), more than 2 per minute and less than 4 frags per tk */ ( actor->state.teamkills >= -scl.banthreshold && - actor->state.teamkills * 30 * 1000 > gamemillis && + actor->state.teamkills * 30 * 1000 > sg->gamemillis && actor->state.frags < 4 * actor->state.teamkills ) ) { addban(actor, DISC_AUTOBAN); @@ -1629,7 +1927,7 @@ void serverdamage(client *target, client *actor, int damage, int gun, bool gib, else if( actor->state.frags < scl.kickthreshold || /** teamkilling more than 5 (defaults), more than 1 tk per minute and less than 4 frags per tk */ ( actor->state.teamkills >= -scl.kickthreshold && - actor->state.teamkills * 60 * 1000 > gamemillis && + actor->state.teamkills * 60 * 1000 > sg->gamemillis && actor->state.frags < 4 * actor->state.teamkills ) ) disconnect_client(actor->clientnum, DISC_AUTOKICK); } } else if ( target!=actor && isteam(target->team, actor->team) ) check_ffire (target, actor, damage); // friendly fire counter @@ -1643,30 +1941,30 @@ void updatesdesc(const char *newdesc, ENetAddress *caller = NULL) { if(!newdesc || !newdesc[0] || !updatedescallowed()) { - copystring(servdesc_current, scl.servdesc_full); - custom_servdesc = false; + copystring(sg->servdesc_current, scl.servdesc_full); + sg->custom_servdesc = false; } else { - formatstring(servdesc_current)("%s%s%s", scl.servdesc_pre, newdesc, scl.servdesc_suf); - custom_servdesc = true; - if(caller) servdesc_caller = *caller; + formatstring(sg->servdesc_current)("%s%s%s", scl.servdesc_pre, newdesc, scl.servdesc_suf); + sg->custom_servdesc = true; + if(caller) sg->servdesc_caller = *caller; } } int canspawn(client *c) // beware: canspawn() doesn't check m_arena! { if(!c || c->type == ST_EMPTY || !c->isauthed || !team_isvalid(c->team) || - (c->type == ST_TCPIP && (c->state.lastdeath > 0 ? gamemillis - c->state.lastdeath : servmillis - c->connectmillis) < (m_arena ? 0 : (m_flags ? 5000 : 2000))) || + (c->type == ST_TCPIP && (c->state.lastdeath > 0 ? sg->gamemillis - c->state.lastdeath : servmillis - c->connectmillis) < (m_arena ? 0 : (m_flags ? 5000 : 2000))) || (c->type == ST_TCPIP && (servmillis - c->connectmillis < 1000 + c->state.reconnections * 2000 && - gamemillis > 10000 && totalclients > 3 && !team_isspect(c->team)))) return SP_OK_NUM; // equivalent to SP_DENY + sg->gamemillis > 10000 && totalclients > 3 && !team_isspect(c->team)))) return SP_OK_NUM; // equivalent to SP_DENY if(!c->isonrightmap) return SP_WRONGMAP; - if(mastermode == MM_MATCH && matchteamsize) + if(sg->mastermode == MM_MATCH && sg->matchteamsize) { if(c->team == TEAM_SPECT || (team_isspect(c->team) && !m_teammode)) return SP_SPECT; if(c->team == TEAM_CLA_SPECT || c->team == TEAM_RVSF_SPECT) { - if(numteamclients()[team_base(c->team)] >= matchteamsize) return SP_SPECT; + if(numteamclients()[team_base(c->team)] >= sg->matchteamsize) return SP_SPECT; else return SP_REFILLMATCH; } } @@ -1675,7 +1973,7 @@ int canspawn(client *c) // beware: canspawn() doesn't check m_arena! void autospawncheck() { - if(mastermode != MM_MATCH || !m_autospawn || interm) return; + if(sg->mastermode != MM_MATCH || !m_autospawn || sg->interm) return; loopv(clients) if(clients[i]->type!=ST_EMPTY && clients[i]->isauthed && team_isactive(clients[i]->team)) { @@ -1696,17 +1994,17 @@ bool updateclientteam(int cln, int newteam, int ftr) client &cl = *clients[cln]; if(cl.team == newteam && ftr != FTR_AUTOTEAM) return true; // no change int *teamsizes = numteamclients(cln); - if( mastermode == MM_OPEN && cl.state.forced && ftr == FTR_PLAYERWISH && + if( sg->mastermode == MM_OPEN && cl.state.forced && ftr == FTR_PLAYERWISH && newteam < TEAM_SPECT && team_base(cl.team) != team_base(newteam) ) return false; // no free will changes to forced people if(newteam == TEAM_ANYACTIVE) // when spawning from spect { - if(mastermode == MM_MATCH && cl.team < TEAM_SPECT) + if(sg->mastermode == MM_MATCH && cl.team < TEAM_SPECT) { newteam = team_base(cl.team); } else { - if(autoteam && teamsizes[TEAM_CLA] != teamsizes[TEAM_RVSF]) newteam = teamsizes[TEAM_CLA] < teamsizes[TEAM_RVSF] ? TEAM_CLA : TEAM_RVSF; + if(sg->autoteam && teamsizes[TEAM_CLA] != teamsizes[TEAM_RVSF]) newteam = teamsizes[TEAM_CLA] < teamsizes[TEAM_RVSF] ? TEAM_CLA : TEAM_RVSF; else { // join weaker team int teamscore[2] = {0, 0}, sum = calcscores(); @@ -1720,23 +2018,23 @@ bool updateclientteam(int cln, int newteam, int ftr) } if(ftr == FTR_PLAYERWISH) { - if(mastermode == MM_MATCH && matchteamsize && m_teammode) + if(sg->mastermode == MM_MATCH && sg->matchteamsize && m_teammode) { if(newteam != TEAM_SPECT && (team_base(newteam) != team_base(cl.team) || !m_teammode)) return false; // no switching sides in match mode when teamsize is set } if(team_isactive(newteam)) { if(!m_teammode && cl.state.state == CS_ALIVE) return false; // no comments - if(mastermode == MM_MATCH) + if(sg->mastermode == MM_MATCH) { - if(m_teammode && matchteamsize && teamsizes[newteam] >= matchteamsize) return false; // ensure maximum team size + if(m_teammode && sg->matchteamsize && teamsizes[newteam] >= sg->matchteamsize) return false; // ensure maximum team size } else { - if(m_teammode && autoteam && teamsizes[newteam] > teamsizes[team_opposite(newteam)]) return false; // don't switch to an already bigger team + if(m_teammode && sg->autoteam && teamsizes[newteam] > teamsizes[team_opposite(newteam)]) return false; // don't switch to an already bigger team } } - else if(mastermode != MM_MATCH || !m_teammode) newteam = TEAM_SPECT; // only match mode (team) has more than one spect team + else if(sg->mastermode != MM_MATCH || !m_teammode) newteam = TEAM_SPECT; // only match mode (team) has more than one spect team } if(cl.team == newteam && ftr != FTR_AUTOTEAM) return true; // no change if(cl.team != newteam) sdropflag(cl.clientnum); @@ -1745,7 +2043,7 @@ bool updateclientteam(int cln, int newteam, int ftr) if(ftr != FTR_INFO && !team_isspect(newteam) && team_isspect(cl.team)) sendspawn(&cl); if (team_isspect(newteam)) { cl.state.state = CS_SPECTATE; - cl.state.lastdeath = gamemillis; + cl.state.lastdeath = sg->gamemillis; } cl.team = newteam; return true; @@ -1768,7 +2066,7 @@ void shuffleteams(int ftr = FTR_AUTOTEAM) { int numplayers = numclients(); int team, sums = calcscores(); - if(gamemillis < 2 * 60 *1000) + if(sg->gamemillis < 2 * 60 *1000) { // random int teamsize[2] = {0, 0}; loopv(clients) if(clients[i]->type!=ST_EMPTY && clients[i]->isonrightmap && !team_isspect(clients[i]->team)) // only shuffle active players @@ -1806,106 +2104,9 @@ void shuffleteams(int ftr = FTR_AUTOTEAM) } } -bool balanceteams(int ftr) // pro vs noobs never more -{ - if(mastermode != MM_OPEN || totalclients < 3 ) return true; - int tsize[2] = {0, 0}, tscore[2] = {0, 0}; - int totalscore = 0, nplayers = 0; - int flagmult = (m_ctf ? 50 : (m_htf ? 25 : 12)); - - loopv(clients) if(clients[i]->type!=ST_EMPTY) - { - client *c = clients[i]; - if(c->isauthed && team_isactive(c->team)) - { - int time = servmillis - c->connectmillis + 5000; - if ( time > gamemillis ) time = gamemillis + 5000; - tsize[c->team]++; - // effective score per minute, thanks to wtfthisgame for the nice idea - // in a normal game, normal players will do 500 points in 10 minutes - c->eff_score = c->state.points * 60 * 1000 / time + c->state.points / 6 + c->state.flagscore * flagmult; - tscore[c->team] += c->eff_score; - nplayers++; - totalscore += c->state.points; - } - } - - int h = 0, l = 1; - if ( tscore[1] > tscore[0] ) { h = 1; l = 0; } - if ( 2 * tscore[h] < 3 * tscore[l] || totalscore < nplayers * 100 ) return true; - if ( tscore[h] > 3 * tscore[l] && tscore[h] > 150 * nplayers ) - { -// sendf(-1, 1, "ri2", SV_SERVERMODE, sendservermode(false) | AT_SHUFFLE); - shuffleteams(); - return true; - } - - float diffscore = tscore[h] - tscore[l]; - - int besth = 0, hid = -1; - int bestdiff = 0, bestpair[2] = {-1, -1}; - if ( tsize[h] - tsize[l] > 0 ) // the h team has more players, so we will force only one player - { - loopv(clients) if( clients[i]->type!=ST_EMPTY ) - { - client *c = clients[i]; // loop for h - // client from the h team, not forced in this game, and without the flag - if( c->isauthed && c->team == h && !c->state.forced && clienthasflag(i) < 0 ) - { - // do not exchange in the way that weaker team becomes the stronger or the change is less than 20% effective - if ( 2 * c->eff_score <= diffscore && 10 * c->eff_score >= diffscore && c->eff_score > besth ) - { - besth = c->eff_score; - hid = i; - } - } - } - if ( hid >= 0 ) - { - updateclientteam(hid, l, ftr); - clients[hid]->at3_lastforce = gamemillis; - clients[hid]->state.forced = true; - return true; - } - } else { // the h score team has less or the same player number, so, lets exchange - loopv(clients) if(clients[i]->type!=ST_EMPTY) - { - client *c = clients[i]; // loop for h - if( c->isauthed && c->team == h && !c->state.forced && clienthasflag(i) < 0 ) - { - loopvj(clients) if(clients[j]->type!=ST_EMPTY && j != i ) - { - client *cj = clients[j]; // loop for l - if( cj->isauthed && cj->team == l && !cj->state.forced && clienthasflag(j) < 0 ) - { - int pairdiff = 2 * (c->eff_score - cj->eff_score); - if ( pairdiff <= diffscore && 5 * pairdiff >= diffscore && pairdiff > bestdiff ) - { - bestdiff = pairdiff; - bestpair[h] = i; - bestpair[l] = j; - } - } - } - } - } - if ( bestpair[h] >= 0 && bestpair[l] >= 0 ) - { - updateclientteam(bestpair[h], l, ftr); - updateclientteam(bestpair[l], h, ftr); - clients[bestpair[h]]->at3_lastforce = clients[bestpair[l]]->at3_lastforce = gamemillis; - clients[bestpair[h]]->state.forced = clients[bestpair[l]]->state.forced = true; - return true; - } - } - return false; -} - -int lastbalance = 0, waitbalance = 2 * 60 * 1000; - bool refillteams(bool now, int ftr) // force only minimal amounts of players { - if(mastermode == MM_MATCH) return false; + if(sg->mastermode == MM_MATCH) return false; static int lasttime_eventeams = 0; int teamsize[2] = {0, 0}, teamscore[2] = {0, 0}, moveable[2] = {0, 0}; bool switched = false; @@ -1933,10 +2134,10 @@ bool refillteams(bool now, int ftr) // force only minimal amounts of players int allplayers = teamsize[0] + teamsize[1]; int diffnum = teamsize[bigteam] - teamsize[!bigteam]; int diffscore = teamscore[bigteam] - teamscore[!bigteam]; - if(lasttime_eventeams > gamemillis) lasttime_eventeams = 0; + if(lasttime_eventeams > sg->gamemillis) lasttime_eventeams = 0; if(diffnum > 1) { - if(now || gamemillis - lasttime_eventeams > 8000 + allplayers * 1000 || diffnum > 2 + allplayers / 10) + if(now || sg->gamemillis - lasttime_eventeams > 8000 + allplayers * 1000 || diffnum > 2 + allplayers / 10) { // time to even out teams loopv(clients) if(clients[i]->type!=ST_EMPTY && clients[i]->team != bigteam) clients[i]->at3_dontmove = true; // dont move small team players @@ -1950,7 +2151,7 @@ bool refillteams(bool now, int ftr) // force only minimal amounts of players { int fit = targetscore - clients[i]->at3_score; if(fit < 0 ) fit = -(fit * 15) / 10; // avoid too good players - int forcedelay = clients[i]->at3_lastforce ? (1000 - (gamemillis - clients[i]->at3_lastforce) / (5 * 60)) : 0; + int forcedelay = clients[i]->at3_lastforce ? (1000 - (sg->gamemillis - clients[i]->at3_lastforce) / (5 * 60)) : 0; if(forcedelay > 0) fit += (fit * forcedelay) / 600; // avoid lately forced players if(fit < bestfit + fit * rnd(100) / 400) // search 'almost' best fit { @@ -1966,7 +2167,7 @@ bool refillteams(bool now, int ftr) // force only minimal amounts of players { diffnum -= 2; diffscore -= 2 * clients[pick]->at3_score; - clients[pick]->at3_lastforce = gamemillis; // try not to force this player again for the next 5 minutes + clients[pick]->at3_lastforce = sg->gamemillis; // try not to force this player again for the next 5 minutes switched = true; } } @@ -1974,22 +2175,7 @@ bool refillteams(bool now, int ftr) // force only minimal amounts of players } if(diffnum < 2) { - if ( ( gamemillis - lastbalance ) > waitbalance && ( gamelimit - gamemillis ) > 4*60*1000 ) - { - if ( balanceteams (ftr) ) - { - waitbalance = 2 * 60 * 1000 + gamemillis / 3; - switched = true; - } - else waitbalance = 20 * 1000; - lastbalance = gamemillis; - } - else if ( lastbalance > gamemillis ) - { - lastbalance = 0; - waitbalance = 2 * 60 * 1000; - } - lasttime_eventeams = gamemillis; + lasttime_eventeams = sg->gamemillis; } return switched; } @@ -1999,27 +2185,30 @@ void resetserver(const char *newname, int newmode, int newtime) if(m_demo) enddemoplayback(); else enddemorecord(); - smode = newmode; - copystring(smapname, newname); + sg->free(); // for now: clean out old servergame states + sg->curmap = NULL; - minremain = newtime > 0 ? newtime : defaultgamelimit(newmode); - gamemillis = 0; - gamelimit = minremain*60000; - arenaround = arenaroundstartmillis = 0; - memset(&smapstats, 0, sizeof(smapstats)); + sg->smode = newmode; + copystring(sg->smapname, newname); + sg->srvgamesalt = (rnd(0x1000000)*((servmillis%4200)+1)) ^ rnd(0x1000000); - interm = nextsendscore = 0; - lastfillup = servmillis; - sents.shrink(0); - if(mastermode == MM_PRIVATE) + sg->minremain = newtime > 0 ? newtime : defaultgamelimit(newmode); + sg->gamemillis = 0; + sg->gamelimit = sg->minremain * 60000; + sg->arenaround = sg->arenaroundstartmillis = 0; + + sg->interm = sg->nextsendscore = 0; + sg->lastfillup = servmillis; + sg->sents.shrink(0); + if(sg->mastermode == MM_PRIVATE) { loopv(savedscores) savedscores[i].valid = false; } else savedscores.shrink(0); ctfreset(); - nextmapname[0] = '\0'; - forceintermission = false; + sg->nextmapname[0] = '\0'; + sg->forceintermission = false; } void startdemoplayback(const char *newname) @@ -2041,69 +2230,81 @@ void startgame(const char *newname, int newmode, int newtime, bool notify) bool lastteammode = m_teammode; resetserver(newname, newmode, newtime); // beware: may clear *newname - if(custom_servdesc && findcnbyaddress(&servdesc_caller) < 0) + if(sg->custom_servdesc && findcnbyaddress(&sg->servdesc_caller) < 0) { updatesdesc(NULL); if(notify) { sendservmsg("server description reset to default"); - logline(ACLOG_INFO, "server description reset to '%s'", servdesc_current); + mlog(ACLOG_INFO, "server description reset to '%s'", sg->servdesc_current); } } - int maploc = MAP_VOID; - mapstats *ms = getservermapstats(smapname, isdedicated, &maploc); - mapbuffer.clear(); - if(isdedicated && distributablemap(maploc)) mapbuffer.load(); - if(ms) + servermap *sm = NULL; + if(isdedicated) + { // dedicated server: only use pre-loaded maps + sm = getservermap(sg->smapname); + } +#ifndef STANDALONE + else + { // local server: load map temporarily + static servermap *localmap = NULL; + DELETEP(localmap); + const char *lpath = checklocalmap(sg->smapname); + if(lpath) localmap = new servermap(sg->smapname, lpath); + if(localmap) + { + localmap->load(); + if(localmap->isok) + { + sm = localmap; + if(!(sm->entstats.modes_possible & 1 << sg->smode)) conoutf("\f3map %s does not support game mode %s", sm->fname, fullmodestr(sg->smode)); + } + else conoutf("\f3local server failed to load map \"%s%s\", error: %s", lpath, sg->smapname, localmap->err); + } + } +#endif + sg->curmap = sm; + if(sm) { - smapstats = *ms; + sg->layout = sm->getlayout(); // get floorplan + loopi(2) { - sflaginfo &f = sflaginfos[i]; - if(smapstats.flags[i] == 1) // don't check flag positions, if there is more than one flag per team + sflaginfo &f = sg->sflaginfos[i]; + if(sg->curmap->entstats.hasflags) // don't check flag positions, if there is more than one flag per team { - short *fe = smapstats.entposs + smapstats.flagents[i] * 3; - f.x = *fe; - fe++; - f.y = *fe; + f.x = sg->curmap->entpos_x[sg->curmap->entstats.flagents[i]]; + f.y = sg->curmap->entpos_y[sg->curmap->entstats.flagents[i]]; } else f.x = f.y = -1; - } - if (smapstats.flags[0] == 1 && smapstats.flags[1] == 1) - { - sflaginfo &f0 = sflaginfos[0], &f1 = sflaginfos[1]; - FlagFlag = pow2(f0.x - f1.x) + pow2(f0.y - f1.y); - coverdist = FlagFlag > 6 * COVERDIST ? COVERDIST : FlagFlag / 6; + f.actor_cn = -1; } entity e; - loopi(smapstats.hdr.numents) + loopi(sg->curmap->numents) { - e.type = smapstats.enttypes[i]; - e.transformtype(smode); - server_entity se = { e.type, false, false, false, 0, smapstats.entposs[i * 3], smapstats.entposs[i * 3 + 1]}; - sents.add(se); - if(e.fitsmode(smode)) sents[i].spawned = sents[i].legalpickup = true; + e.type = sg->curmap->enttypes[i]; + e.transformtype(sg->smode); + server_entity se = { e.type, false, false, false, 0, sg->curmap->entpos_x[i], sg->curmap->entpos_y[i] }; + sg->sents.add(se); + if(e.fitsmode(sg->smode)) sg->sents[i].spawned = sg->sents[i].legalpickup = true; } - mapbuffer.setrevision(); - logline(ACLOG_INFO, "Map height density information for %s: H = %.2f V = %d, A = %d and MA = %d", smapname, Mheight, Mvolume, Marea, Mopen); - items_blocked = false; } - else if(isdedicated) sendservmsg("\f3server error: map not found - please start another map or send this map to the server"); + else if(isdedicated && sg->smode != GMODE_COOPEDIT) fatal("servermap not found"); // FIXME: coop should also start with a map if(notify) { // change map - sendf(-1, 1, "risiii", SV_MAPCHANGE, smapname, smode, mapbuffer.available(), mapbuffer.revision); - if(smode>1 || (smode==0 && numnonlocalclients()>0)) sendf(-1, 1, "ri3", SV_TIMEUP, gamemillis, gamelimit); + sendf(-1, 1, "risiiii", SV_MAPCHANGE, sg->smapname, sg->smode, sm && sm->isdistributable() ? sm->cgzlen : 0, sm && sm->isdistributable() ? sm->maprevision : 0, sg->srvgamesalt); + if(sg->smode > 1 || (sg->smode == 0 && numnonlocalclients() > 0)) sendf(-1, 1, "ri3", SV_TIMEUP, sg->gamemillis, sg->gamelimit); } packetbuf q(MAXTRANS, ENET_PACKET_FLAG_RELIABLE); send_item_list(q); // always send the item list when a game starts sendpacket(-1, 1, q.finalize()); - defformatstring(gsmsg)("Game start: %s on %s, %d players, %d minutes, mastermode %d, ", modestr(smode), smapname, numclients(), minremain, mastermode); - if(mastermode == MM_MATCH) concatformatstring(gsmsg, "teamsize %d, ", matchteamsize); - if(ms) concatformatstring(gsmsg, "(map rev %d/%d, %s, 'getmap' %sprepared)", smapstats.hdr.maprevision, smapstats.cgzsize, maplocstr[maploc], mapbuffer.available() ? "" : "not "); - else concatformatstring(gsmsg, "error: failed to preload map (map: %s)", maplocstr[maploc]); - logline(ACLOG_INFO, "\n%s", gsmsg); + defformatstring(gsmsg)("Game start: %s on %s, %d players, %d minutes, mastermode %d, ", modestr(sg->smode), sg->smapname, numclients(), sg->minremain, sg->mastermode); + if(sg->mastermode == MM_MATCH) concatformatstring(gsmsg, "teamsize %d, ", sg->matchteamsize); + if(sm) concatformatstring(gsmsg, "(map rev %d/%d, %s, 'getmap' %sprepared)", sm->maprevision, sm->cgzlen, sm->getpathdesc(), sm->isdistributable() ? "" : "not "); + else concatformatstring(gsmsg, "error: failed to preload map"); + mlog(ACLOG_INFO, "\n%s", gsmsg); if(m_arena) distributespawns(); if(notify) { @@ -2112,7 +2313,7 @@ void startgame(const char *newname, int newmode, int newtime, bool notify) { if(!lastteammode) shuffleteams(FTR_INFO); - else if(autoteam) + else if(sg->autoteam) refillteams(true, FTR_INFO); } // prepare spawns; players will spawn, once they've loaded the correct map @@ -2169,7 +2370,7 @@ void addgban(const char *name) { client &c = *clients[i]; if(c.type!=ST_TCPIP) continue; - if(checkgban(c.peer->address.host)) disconnect_client(c.clientnum, DISC_BANREFUSE); + if(checkgban(c.peer->address.host)) disconnect_client(c.clientnum, DISC_BANNED); } } @@ -2283,7 +2484,7 @@ bool svote(int sender, int vote, ENetPacket *msg) // true if the vote was placed sendpacket(-1, 1, msg, sender); clients[sender]->vote = vote; - logline(ACLOG_DEBUG,"[%s] client %s voted %s", clients[sender]->hostname, clients[sender]->name, vote == VOTE_NO ? "no" : "yes"); + mlog(ACLOG_DEBUG,"[%s] client %s voted %s", clients[sender]->hostname, clients[sender]->name, vote == VOTE_NO ? "no" : "yes"); curvote->evaluate(); return true; } @@ -2297,14 +2498,14 @@ void scallvotesuc(voteinfo *v) clients[v->owner]->lastvotecall = servmillis; clients[v->owner]->nvotes--; // successful votes do not count as abuse sendf(v->owner, 1, "ri", SV_CALLVOTESUC); - logline(ACLOG_INFO, "[%s] client %s called a vote: %s", clients[v->owner]->hostname, clients[v->owner]->name, v->action && *v->action->desc ? v->action->desc : "[unknown]"); + mlog(ACLOG_INFO, "[%s] client %s called a vote: %s", clients[v->owner]->hostname, clients[v->owner]->name, v->action && *v->action->desc ? v->action->desc : "[unknown]"); } void scallvoteerr(voteinfo *v, int error) { if(!valid_client(v->owner)) return; sendf(v->owner, 1, "ri2", SV_CALLVOTEERR, error); - logline(ACLOG_INFO, "[%s] client %s failed to call a vote: %s (%s)", clients[v->owner]->hostname, clients[v->owner]->name, v->action && *v->action->desc ? v->action->desc : "[unknown]", voteerrorstr(error)); + mlog(ACLOG_INFO, "[%s] client %s failed to call a vote: %s (%s)", clients[v->owner]->hostname, clients[v->owner]->name, v->action && *v->action->desc ? v->action->desc : "[unknown]", voteerrorstr(error)); } bool map_queued = false; @@ -2418,8 +2619,8 @@ void changeclientrole(int client, int role, char *pwd, bool force) clients[client]->role = role; sendserveropinfo(-1); if(pd.line > -1) - logline(ACLOG_INFO,"[%s] player %s used admin password in line %d", clients[client]->hostname, clients[client]->name[0] ? clients[client]->name : "[unnamed]", pd.line); - logline(ACLOG_INFO,"[%s] set role of player %s to %s", clients[client]->hostname, clients[client]->name[0] ? clients[client]->name : "[unnamed]", role == CR_ADMIN ? "admin" : "normal player"); // flowtron : connecting players haven't got a name yet (connectadmin) + mlog(ACLOG_INFO,"[%s] player %s used admin password in line %d", clients[client]->hostname, clients[client]->name[0] ? clients[client]->name : "[unnamed]", pd.line); + mlog(ACLOG_INFO,"[%s] set role of player %s to %s", clients[client]->hostname, clients[client]->name[0] ? clients[client]->name : "[unnamed]", role == CR_ADMIN ? "admin" : "normal player"); // flowtron : connecting players haven't got a name yet (connectadmin) if(role > CR_DEFAULT) sendiplist(client); } else if(pwd && pwd[0]) disconnect_client(client, DISC_SOPLOGINFAIL); // avoid brute-force @@ -2430,7 +2631,7 @@ void senddisconnectedscores(int cn) { packetbuf p(MAXTRANS, ENET_PACKET_FLAG_RELIABLE); putint(p, SV_DISCSCORES); - if(mastermode == MM_MATCH) + if(sg->mastermode == MM_MATCH) { loopv(savedscores) { @@ -2450,14 +2651,9 @@ void senddisconnectedscores(int cn) sendpacket(cn, 1, p.finalize()); } -const char *disc_reason(int reason) -{ - static const char *disc_reasons[] = { "normal", "error - end of packet", "error - client num", "vote-kicked from the server", "vote-banned from the server", "error - tag type", "connection refused - you have been banned from this server", "incorrect password", "unsuccessful administrator login", "the server is FULL - try again later", "servers mastermode is \"private\" - wait until the servers mastermode is \"open\"", "auto-kick - your score dropped below the servers threshold", "auto-ban - your score dropped below the servers threshold", "duplicate connection", "inappropriate nickname", "error - packet flood", "auto-kick - excess spam detected", "auto-kick - inactivity detected", "auto-kick - team killing detected", "auto-kick - abnormal client behavior detected" }; - return reason >= 0 && (size_t)reason < sizeof(disc_reasons)/sizeof(disc_reasons[0]) ? disc_reasons[reason] : "unknown"; -} - void disconnect_client(int n, int reason) { + ASSERT(ismainthread()); if(!clients.inrange(n) || clients[n]->type!=ST_TCPIP) return; sdropflag(n); client &c = *clients[n]; @@ -2473,82 +2669,17 @@ void disconnect_client(int n, int reason) } } int sp = (servmillis - c.connectmillis) / 1000; - if(reason>=0) logline(ACLOG_INFO, "[%s] disconnecting client %s (%s) cn %d, %d seconds played%s", c.hostname, c.name, disc_reason(reason), n, sp, scoresaved); - else logline(ACLOG_INFO, "[%s] disconnected client %s cn %d, %d seconds played%s", c.hostname, c.name, n, sp, scoresaved); + if(reason>=0) mlog(ACLOG_INFO, "[%s] disconnecting client %s (%s) cn %d, %d seconds played%s", c.hostname, c.name, disc_reason(reason), n, sp, scoresaved); + else mlog(ACLOG_INFO, "[%s] disconnected client %s cn %d, %d seconds played%s", c.hostname, c.name, n, sp, scoresaved); totalclients--; c.peer->data = (void *)-1; if(reason>=0) enet_peer_disconnect(c.peer, reason); clients[n]->zap(); sendf(-1, 1, "rii", SV_CDIS, n); if(curvote) curvote->evaluate(); - if(*scoresaved && mastermode == MM_MATCH) senddisconnectedscores(-1); + if(*scoresaved && sg->mastermode == MM_MATCH) senddisconnectedscores(-1); } -// for AUTH: WIP - -client *findauth(uint id) -{ - loopv(clients) if(clients[i]->authreq == id) return clients[i]; - return NULL; -} - -void authfailed(uint id) -{ - client *cl = findauth(id); - if(!cl) return; - cl->authreq = 0; -} - -void authsucceeded(uint id) -{ - client *cl = findauth(id); - if(!cl) return; - cl->authreq = 0; - logline(ACLOG_INFO, "player authenticated: %s", cl->name); - defformatstring(auth4u)("player authenticated: %s", cl->name); - sendf(-1, 1, "ris", SV_SERVMSG, auth4u); - //setmaster(cl, true, "", ci->authname);//TODO? compare to sauerbraten -} - -void authchallenged(uint id, const char *val) -{ - client *cl = findauth(id); - if(!cl) return; - sendf(cl->clientnum, 1, "risis", SV_AUTHCHAL, "", id, val); -} - -uint nextauthreq = 0; - -void tryauth(client *cl, const char *user) -{ - extern bool requestmasterf(const char *fmt, ...); - if(!nextauthreq) nextauthreq = 1; - cl->authreq = nextauthreq++; - filtertext(cl->authname, user, FTXT__AUTH, 100); - if(!requestmasterf("reqauth %u %s\n", cl->authreq, cl->authname)) - { - cl->authreq = 0; - sendf(cl->clientnum, 1, "ris", SV_SERVMSG, "not connected to authentication server"); - } -} - -void answerchallenge(client *cl, uint id, char *val) -{ - if(cl->authreq != id) return; - extern bool requestmasterf(const char *fmt, ...); - for(char *s = val; *s; s++) - { - if(!isxdigit(*s)) { *s = '\0'; break; } - } - if(!requestmasterf("confauth %u %s\n", id, val)) - { - cl->authreq = 0; - sendf(cl->clientnum, 1, "ris", SV_SERVMSG, "not connected to authentication server"); - } -} - -// :for AUTH - void sendiplist(int receiver, int cn) { if(!valid_client(receiver)) return; @@ -2558,7 +2689,7 @@ void sendiplist(int receiver, int cn) && (clients[i]->clientnum == cn || cn == -1)) { putint(p, i); - putint(p, isbigendian() ? endianswap(clients[i]->peer->address.host) : clients[i]->peer->address.host); + putint(p, clients[i]->ip); } putint(p, -1); sendpacket(receiver, 1, p.finalize()); @@ -2601,7 +2732,8 @@ bool restorescore(client &c) void sendservinfo(client &c) { - sendf(c.clientnum, 1, "ri5", SV_SERVINFO, c.clientnum, isdedicated ? SERVER_PROTOCOL_VERSION : PROTOCOL_VERSION, c.salt, scl.serverpassword[0] ? 1 : 0); + sendf(c.clientnum, 1, "ri6IIIi3k", SV_SERVINFO, c.clientnum, isdedicated ? SERVER_PROTOCOL_VERSION : PROTOCOL_VERSION, c.needsauth ? 1 : 0, scl.serverpassword[0] ? 1 : 0, c.salt, + servownip, c.ip, c.ip_censored, (c.connectclock = servclock), c.country[0], c.country[1], spk); } void putinitclient(client &c, packetbuf &p) @@ -2612,9 +2744,11 @@ void putinitclient(client &c, packetbuf &p) putint(p, c.skin[TEAM_CLA]); putint(p, c.skin[TEAM_RVSF]); putint(p, c.team); - enet_uint32 ip = 0; - if(c.type == ST_TCPIP) ip = c.peer->address.host & 0xFFFFFF; - putint(p, isbigendian() ? endianswap(ip) : ip); + putint(p, c.maxroll); + putint(p, c.maxrolleffect); + putint(p, c.ffov); + putint(p, c.scopefov); + putint(p, c.ip_censored); } void sendinitclient(client &c) @@ -2636,26 +2770,27 @@ void welcomeinitclient(packetbuf &p, int exclude = -1) void welcomepacket(packetbuf &p, int n) { - if(!smapname[0]) maprot.next(false); + if(!sg->smapname[0]) maprot.next(false); client *c = valid_client(n) ? clients[n] : NULL; int numcl = numclients(); putint(p, SV_WELCOME); - putint(p, smapname[0] && !m_demo ? numcl : -1); - if(smapname[0] && !m_demo) + putint(p, sg->smapname[0] && !m_demo ? numcl : -1); + if(sg->smapname[0] && !m_demo) { putint(p, SV_MAPCHANGE); - sendstring(smapname, p); - putint(p, smode); - putint(p, mapbuffer.available()); - putint(p, mapbuffer.revision); - if(smode>1 || (smode==0 && numnonlocalclients()>0)) + sendstring(sg->smapname, p); + putint(p, sg->smode); + putint(p, sg->curmap && sg->curmap->isdistributable() ? sg->curmap->cgzlen : 0); + putint(p, sg->curmap && sg->curmap->isdistributable() ? sg->curmap->maprevision : 0); + putint(p, sg->srvgamesalt); + if(sg->smode>1 || (sg->smode==0 && numnonlocalclients()>0)) { putint(p, SV_TIMEUP); - putint(p, (gamemillis>=gamelimit || forceintermission) ? gamelimit : gamemillis); - putint(p, gamelimit); - //putint(p, minremain*60); + putint(p, (sg->gamemillis>=sg->gamelimit || sg->forceintermission) ? sg->gamelimit : sg->gamemillis); + putint(p, sg->gamelimit); + //putint(p, sg->minremain*60); } send_item_list(p); // this includes the flags } @@ -2663,7 +2798,7 @@ void welcomepacket(packetbuf &p, int n) if(c) { if(c->type == ST_TCPIP && serveroperator() != -1) sendserveropinfo(n); - c->team = mastermode == MM_MATCH && sc ? team_tospec(sc->team) : TEAM_SPECT; + c->team = sg->mastermode == MM_MATCH && sc ? team_tospec(sc->team) : TEAM_SPECT; putint(p, SV_SETTEAM); putint(p, n); putint(p, c->team | (FTR_INFO << 4)); @@ -2699,8 +2834,8 @@ void welcomepacket(packetbuf &p, int n) } putint(p, SV_SERVERMODE); putint(p, sendservermode(false)); - const char *motd = scl.motd[0] ? scl.motd : infofiles.getmotd(c ? c->lang : ""); - if(motd) + const char *motd = scl.motd[0] ? scl.motd : serverinfomotd.getmsg(); + if(*motd) { putint(p, SV_TEXT); sendstring(motd, p); @@ -2729,29 +2864,126 @@ int checktype(int type, client *cl) if(type < 0 || type >= SV_NUM) return -1; // server only messages static int servtypes[] = { SV_SERVINFO, SV_WELCOME, SV_INITCLIENT, SV_POSN, SV_CDIS, SV_GIBDIED, SV_DIED, - SV_GIBDAMAGE, SV_DAMAGE, SV_HITPUSH, SV_SHOTFX, SV_AUTHREQ, SV_AUTHCHAL, + SV_GIBDAMAGE, SV_DAMAGE, SV_HITPUSH, SV_SHOTFX, SV_SPAWNSTATE, SV_SPAWNDENY, SV_FORCEDEATH, SV_RESUME, SV_DISCSCORES, SV_TIMEUP, SV_ITEMACC, SV_MAPCHANGE, SV_ITEMSPAWN, SV_PONG, - SV_SERVMSG, SV_ITEMLIST, SV_FLAGINFO, SV_FLAGMSG, SV_FLAGCNT, + SV_SERVMSG, SV_SERVMSGVERB, SV_ITEMLIST, SV_FLAGINFO, SV_FLAGMSG, SV_FLAGCNT, SV_ARENAWIN, SV_SERVOPINFO, SV_CALLVOTESUC, SV_CALLVOTEERR, SV_VOTERESULT, SV_SETTEAM, SV_TEAMDENY, SV_SERVERMODE, SV_IPLIST, SV_SENDDEMOLIST, SV_SENDDEMO, SV_DEMOPLAYBACK, - SV_CLIENT, SV_HUDEXTRAS, SV_POINTS }; + SV_CLIENT, SV_DEMOCHECKSUM }; // only allow edit messages in coop-edit mode static int edittypes[] = { SV_EDITENT, SV_EDITXY, SV_EDITARCH, SV_EDITBLOCK, SV_EDITD, SV_EDITE, SV_NEWMAP }; if(cl) { loopi(sizeof(servtypes)/sizeof(int)) if(type == servtypes[i]) return -1; - loopi(sizeof(edittypes)/sizeof(int)) if(type == edittypes[i]) return smode==GMODE_COOPEDIT ? type : -1; + loopi(sizeof(edittypes)/sizeof(int)) if(type == edittypes[i]) return sg->smode==GMODE_COOPEDIT ? type : -1; if(++cl->overflow >= 200) return -2; } return type; } + +// process auth calculations in separate thread, since it may take a while + +struct servinfoauth +{ + string clientmsg, servermsg; // client message to verify, server message to sign + uchar clientpubkey[32]; + int type, wantauth, cn, clientsequence, clientmsglen, servermsglen, success; +}; + +ringbuf servinfoauths; +volatile servinfoauth *servinfoauth_dropbox = NULL; +volatile bool servinfoauth_done = false; +sl_semaphore servinfoauth_deferred(0, NULL); + +int deferredprocessingthread(void *nop) +{ + for(;;) + { + servinfoauth_deferred.wait(); + if(!servinfoauth_done && servinfoauths.length()) + { + servinfoauth *sa = servinfoauths.remove(); + servinfoauth_dropbox = sa; + sa->success = 0; + switch(sa->type) + { + case SV_SERVINFO_RESPONSE: + { + if(!sa->wantauth) sa->success = 1; + else if(sa->clientmsglen && ed25519_sign_check((uchar*)sa->clientmsg, sa->clientmsglen + 64, sa->clientpubkey)) + { // client message checks out, now sign the answer + ed25519_sign((uchar*)sa->servermsg, NULL, (uchar*)sa->servermsg, sa->servermsglen, ssk); + sa->success = 1; + } + break; + } + } + servinfoauth_done = true; + } + } + return 0; +} + +void polldeferredprocessing() +{ + static void *deferredprocessingthread_ti = NULL; + if(!deferredprocessingthread_ti) deferredprocessingthread_ti = sl_createthread(deferredprocessingthread, NULL); + + if(servinfoauth_done) + { + servinfoauth *sa = (servinfoauth *)servinfoauth_dropbox; + servinfoauth_done = false; + switch(sa->type) + { + case SV_SERVINFO_RESPONSE: + { + if(clients[sa->cn]->clientsequence == sa->clientsequence) // make sure, it's still the same client using that cn + { + if(!sa->success) disconnect_client(sa->cn, DISC_AUTH); + else + { // continue connecting + client *cl = clients[sa->cn]; + if(sa->wantauth) + { + memcpy(cl->pubkey.u, sa->clientpubkey, 32); + memcpy(cl->servinforesponse, sa->clientmsg, sa->clientmsglen + 64); + bin2hex(cl->pubkeyhex, cl->pubkey.u, 32); + cl->vita = vitas.access(cl->pubkey); + if(!cl->vita) + { + vita_s v; + memset(&v, 0, sizeof(v)); + cl->vita = &vitas.access(cl->pubkey, v); + cl->vita->vs[VS_FIRSTLOGIN] = servclock; + } + if(cl->country[0] != '-') cl->vita->addcc(cl->country); + cl->vita->addip(cl->ip); + cl->vita->vs[VS_LASTLOGIN] = servclock; + cl->needsauth = true; // may have been voluntary, but it's done + int datecodes = 0, *vs = cl->vita->vs; + loopi(VS_NUMCOUNTERS) datecodes |= vs[i] == 1 || (servclock - vs[i]) < 0 ? (1 << i) : 0; + sendf(sa->cn, 1, "riimiss", SV_SERVINFO_CONTD, 1, 64, sa->servermsg, datecodes, cl->vita->clan, cl->vita->publiccomment); + } + else sendf(sa->cn, 1, "rii", SV_SERVINFO_CONTD, 0); + } + } + break; + } + } + delete sa; + } + if(!servinfoauths.empty() && servinfoauth_deferred.getvalue() < 1) servinfoauth_deferred.post(); +} + // server side processing of updates: does very little and most state is tracked client only // could be extended to move more gameplay to server (at expense of lag) +SERVPARLIST(auth_verify_ip, 0, 0, 0, endis, "sVerify server IP reported by the client during auth"); + void process(ENetPacket *packet, int sender, int chan) { ucharbuf p(packet->data, packet->dataLength); @@ -2765,9 +2997,44 @@ void process(ENetPacket *packet, int sender, int chan) int clientrole = CR_DEFAULT; if(chan==0) return; - else if(chan!=1 || getint(p)!=SV_CONNECT) disconnect_client(sender, DISC_TAGT); - else + else if(chan!=1 || ((type = getint(p)) != SV_SERVINFO_RESPONSE && type != SV_CONNECT)) disconnect_client(sender, DISC_TAGT); + else if(type == SV_SERVINFO_RESPONSE) { + if(servinfoauths.full()) disconnect_client(sender, DISC_OVERLOAD); + else + { + string tmp1, tmp2; + servinfoauth *sa = servinfoauths.stage(new servinfoauth); + memset(sa, 0, sizeof(servinfoauth)); + int clientsalt = getint(p), clientclock = getint(p), curpeerport = getint(p); + enet_uint32 curpeerip = getip4(p); + if((sa->wantauth = getint(p))) + { + p.get(sa->clientpubkey, 32); + p.get((uchar*)sa->clientmsg, 64); + } + bool ipfail = servownip && servownip != curpeerip; + if(ipfail) mlog(ACLOG_INFO, "[%s] client reported different server IP %s:%d (known own IP is %s)", cl->hostname, iptoa(curpeerip, tmp1), curpeerport, iptoa(servownip, tmp2)); + formatstring(text)("SERVINFOCHALLENGE<(%d) cn: %d c: %s (%s) s: %s:%d", cl->salt, sender, iptoa(cl->ip_censored, tmp1), cl->country, iptoa(servownip, tmp2), curpeerport); + concatformatstring(text, " %s st: %d ct: %d (%d)>", bin2hex(tmp1, spk, 32), cl->connectclock, clientclock, clientsalt); + sa->clientmsglen = (int)strlen(text); + if(sa->clientmsglen < MAXSTRLEN - 65) memcpy(sa->clientmsg + 64, text, sa->clientmsglen); + else sa->clientmsglen = 0; + formatstring(sa->servermsg)("SERVINFOSIGNED<%s>", bin2hex(tmp1, (uchar*)sa->clientmsg, 64)); + sa->servermsglen = (int)strlen(sa->servermsg); + if((!sa->wantauth && cl->needsauth) || (ipfail && auth_verify_ip)) { delete sa; disconnect_client(sender, DISC_AUTH); } + else + { + sa->type = type; + sa->cn = sender; + sa->clientsequence = cl->clientsequence; + servinfoauths.commit(); + } + } + return; + } + else + { // SV_CONNECT cl->acversion = getint(p); cl->acbuildtype = getint(p); defformatstring(tags)(", AC: %d|%x", cl->acversion, cl->acbuildtype); @@ -2782,27 +3049,31 @@ void process(ENetPacket *packet, int sender, int chan) int wantrole = getint(p), np = getint(p); cl->state.nextprimary = np > 0 && np < NUMGUNS ? np : GUN_ASSAULT; loopi(2) cl->skin[i] = getint(p); + cl->maxroll = getint(p); + cl->maxrolleffect = getint(p); + cl->ffov = getint(p); + cl->scopefov = getint(p); int bantype = getbantype(sender); bool banned = bantype > BAN_NONE; bool srvfull = numnonlocalclients() > scl.maxclients; - bool srvprivate = mastermode == MM_PRIVATE || mastermode == MM_MATCH; - bool matchreconnect = mastermode == MM_MATCH && findscore(*cl, false); + bool srvprivate = sg->mastermode == MM_PRIVATE || sg->mastermode == MM_MATCH; + bool matchreconnect = sg->mastermode == MM_MATCH && findscore(*cl, false); int bl = 0, wl = nickblacklist.checkwhitelist(*cl); if(wl == NWL_PASS) concatstring(tags, ", nickname whitelist match"); if(wl == NWL_UNLISTED) bl = nickblacklist.checkblacklist(cl->name); if(matchreconnect && !banned) { // former player reconnecting to a server in match mode cl->isauthed = true; - logline(ACLOG_INFO, "[%s] %s logged in (reconnect to match)%s", cl->hostname, cl->name, tags); + mlog(ACLOG_INFO, "[%s] %s logged in (reconnect to match)%s", cl->hostname, cl->name, tags); } else if(wl == NWL_IPFAIL || wl == NWL_PWDFAIL) { // nickname matches whitelist, but IP is not in the required range or PWD doesn't match - logline(ACLOG_INFO, "[%s] '%s' matches nickname whitelist: wrong %s%s", cl->hostname, cl->name, wl == NWL_IPFAIL ? "IP" : "PWD", tags); + mlog(ACLOG_INFO, "[%s] '%s' matches nickname whitelist: wrong %s%s", cl->hostname, cl->name, wl == NWL_IPFAIL ? "IP" : "PWD", tags); disconnect_client(sender, DISC_BADNICK); } else if(bl > 0) { // nickname matches blacklist - logline(ACLOG_INFO, "[%s] '%s' matches nickname blacklist line %d%s", cl->hostname, cl->name, bl, tags); + mlog(ACLOG_INFO, "[%s] '%s' matches nickname blacklist line %d%s", cl->hostname, cl->name, bl, tags); disconnect_client(sender, DISC_BADNICK); } else if(passwords.check(cl->name, cl->pwd, cl->salt, &pd, (cl->type==ST_TCPIP ? cl->peer->address.host : 0)) && (!pd.denyadmin || (banned && !srvfull && !srvprivate)) && bantype != BAN_MASTER) // pass admins always through @@ -2821,24 +3092,24 @@ void process(ENetPacket *packet, int sender, int chan) break; } } - logline(ACLOG_INFO, "[%s] %s logged in using the admin password in line %d%s", cl->hostname, cl->name, pd.line, tags); + mlog(ACLOG_INFO, "[%s] %s logged in using the admin password in line %d%s", cl->hostname, cl->name, pd.line, tags); } else if(scl.serverpassword[0] && !(srvprivate || srvfull || banned)) { // server password required if(!strcmp(genpwdhash(cl->name, scl.serverpassword, cl->salt), cl->pwd)) { cl->isauthed = true; - logline(ACLOG_INFO, "[%s] %s client logged in (using serverpassword)%s", cl->hostname, cl->name, tags); + mlog(ACLOG_INFO, "[%s] %s client logged in (using serverpassword)%s", cl->hostname, cl->name, tags); } else disconnect_client(sender, DISC_WRONGPW); } - else if(srvprivate) disconnect_client(sender, DISC_MASTERMODE); + else if(srvprivate) disconnect_client(sender, DISC_PRIVATE); else if(srvfull) disconnect_client(sender, DISC_MAXCLIENTS); - else if(banned) disconnect_client(sender, DISC_BANREFUSE); + else if(banned) disconnect_client(sender, DISC_BANNED); else { cl->isauthed = true; - logline(ACLOG_INFO, "[%s] %s logged in (default)%s", cl->hostname, cl->name, tags); + mlog(ACLOG_INFO, "[%s] %s logged in (default)%s", cl->hostname, cl->name, tags); } } if(!cl->isauthed) return; @@ -2853,6 +3124,7 @@ void process(ENetPacket *packet, int sender, int chan) } } + if(cl->vita) cl->vita->addname(cl->name); sendwelcome(cl); if(restorescore(*cl)) { sendresume(*cl, true); senddisconnectedscores(-1); } else if(cl->type==ST_TCPIP) senddisconnectedscores(sender); @@ -2868,7 +3140,7 @@ void process(ENetPacket *packet, int sender, int chan) } } - if(!cl) { logline(ACLOG_ERROR, " client in process()"); return; } // should never happen anyway + if(!cl) { mlog(ACLOG_ERROR, " client in process()"); return; } // should never happen anyway if(packet->flags&ENET_PACKET_FLAG_RELIABLE) reliablemessages = true; @@ -2898,40 +3170,37 @@ void process(ENetPacket *packet, int sender, int chan) type = checktype(getint(p), cl); #ifdef _DEBUG - if(type!=SV_POS && type!=SV_POSC && type!=SV_CLIENTPING && type!=SV_PING && type!=SV_CLIENT) + if(type!=SV_POS && type!=SV_POSC && type!=SV_POSC2 && type!=SV_POSC3 && type!=SV_POSC4 && type!=SV_CLIENTPING && type!=SV_PING && type!=SV_CLIENT) { DEBUGVAR(cl->name); - ASSERT(type>=0 && type= 0) { DEBUGVAR(messagenames[type]); } protocoldebug(DEBUGCOND); } else protocoldebug(false); #endif - type = checkmessage(cl,type); switch(type) { case SV_TEAMTEXTME: case SV_TEAMTEXT: getstring(text, p); filtertext(text, text, FTXT__CHAT); - trimtrailingwhitespace(text); if(*text) { bool canspeech = forbiddenlist.canspeech(text); if(!spamdetect(cl, text) && canspeech) // team chat { - logline(ACLOG_INFO, "[%s] %s%s says to team %s: '%s'", cl->hostname, type == SV_TEAMTEXTME ? "(me) " : "", cl->name, team_string(cl->team), text); + mlog(ACLOG_INFO, "[%s] %s%s says to team %s: '%s'", cl->hostname, type == SV_TEAMTEXTME ? "(me) " : "", cl->name, team_string(cl->team), text); sendteamtext(text, sender, type); } else { - logline(ACLOG_INFO, "[%s] %s%s says to team %s: '%s', %s", cl->hostname, type == SV_TEAMTEXTME ? "(me) " : "", + mlog(ACLOG_INFO, "[%s] %s%s says to team %s: '%s', %s", cl->hostname, type == SV_TEAMTEXTME ? "(me) " : "", cl->name, team_string(cl->team), text, canspeech ? "SPAM detected" : "Forbidden speech"); if (canspeech) { sendservmsg("\f3Please do not spam; your message was not delivered.", sender); - if ( cl->spamcount > SPAMMAXREPEAT + 2 ) disconnect_client(cl->clientnum, DISC_ABUSE); + if ( cl->spamcount > SPAMMAXREPEAT + 2 ) disconnect_client(cl->clientnum, DISC_SPAM); } else { @@ -2948,32 +3217,33 @@ void process(ENetPacket *packet, int sender, int chan) int mid1 = curmsg, mid2 = p.length(); getstring(text, p); filtertext(text, text, FTXT__CHAT); - trimtrailingwhitespace(text); if(*text) { bool canspeech = forbiddenlist.canspeech(text); if(!spamdetect(cl, text) && canspeech) { - if(mastermode != MM_MATCH || !matchteamsize || team_isactive(cl->team) || (cl->team == TEAM_SPECT && cl->role == CR_ADMIN)) // common chat + if(sg->mastermode != MM_MATCH || !sg->matchteamsize || team_isactive(cl->team) || (cl->team == TEAM_SPECT && cl->role == CR_ADMIN)) // common chat { - logline(ACLOG_INFO, "[%s] %s%s says: '%s'", cl->hostname, type == SV_TEXTME ? "(me) " : "", cl->name, text); + mlog(ACLOG_INFO, "[%s] %s%s says: '%s'", cl->hostname, type == SV_TEXTME ? "(me) " : "", cl->name, text); if(cl->type==ST_TCPIP) while(mid1messages.add(p.buf[mid1++]); QUEUE_STR(text); + ASSERT(SV_TEXT < 126 && SV_TEXTME < 126); + sendf(sender, 1, "riiuis", SV_CLIENT, sender, (int) strlen(text) + 2, type, text); // "local" echo } else // spect chat { - logline(ACLOG_INFO, "[%s] %s%s says to team %s: '%s'", cl->hostname, type == SV_TEAMTEXTME ? "(me) " : "", cl->name, team_string(cl->team), text); + mlog(ACLOG_INFO, "[%s] %s%s says to team %s: '%s'", cl->hostname, type == SV_TEAMTEXTME ? "(me) " : "", cl->name, team_string(cl->team), text); sendteamtext(text, sender, type == SV_TEXTME ? SV_TEAMTEXTME : SV_TEAMTEXT); } } else { - logline(ACLOG_INFO, "[%s] %s%s says: '%s', %s", cl->hostname, type == SV_TEXTME ? "(me) " : "", + mlog(ACLOG_INFO, "[%s] %s%s says: '%s', %s", cl->hostname, type == SV_TEXTME ? "(me) " : "", cl->name, text, canspeech ? "SPAM detected" : "Forbidden speech"); if (canspeech) { sendservmsg("\f3Please do not spam; your message was not delivered.", sender); - if ( cl->spamcount > SPAMMAXREPEAT + 2 ) disconnect_client(cl->clientnum, DISC_ABUSE); + if ( cl->spamcount > SPAMMAXREPEAT + 2 ) disconnect_client(cl->clientnum, DISC_SPAM); } else { @@ -2990,7 +3260,6 @@ void process(ENetPacket *packet, int sender, int chan) int targ = getint(p); getstring(text, p); filtertext(text, text, FTXT__CHAT); - trimtrailingwhitespace(text); if(!valid_client(targ)) break; client *target = clients[targ]; @@ -3000,17 +3269,21 @@ void process(ENetPacket *packet, int sender, int chan) bool canspeech = forbiddenlist.canspeech(text); if(!spamdetect(cl, text) && canspeech) { - bool allowed = !(mastermode == MM_MATCH && cl->team != target->team) && cl->role >= roleconf('t'); - logline(ACLOG_INFO, "[%s] %s says to %s: '%s' (%s)", cl->hostname, cl->name, target->name, text, allowed ? "allowed":"disallowed"); - if(allowed) sendf(target->clientnum, 1, "riis", SV_TEXTPRIVATE, cl->clientnum, text); + bool allowed = !(sg->mastermode == MM_MATCH && cl->team != target->team) && cl->role >= roleconf('t'); + mlog(ACLOG_INFO, "[%s] %s says to %s: '%s' (%s)", cl->hostname, cl->name, target->name, text, allowed ? "allowed":"disallowed"); + if(allowed) + { + sendf(cl->clientnum, 1, "riiis", SV_TEXTPRIVATE, cl->clientnum, target->clientnum, text); // "local" echo + sendf(target->clientnum, 1, "riiis", SV_TEXTPRIVATE, cl->clientnum, -1, text); + } } else { - logline(ACLOG_INFO, "[%s] %s says to %s: '%s', %s", cl->hostname, cl->name, target->name, text, canspeech ? "SPAM detected" : "Forbidden speech"); + mlog(ACLOG_INFO, "[%s] %s says to %s: '%s', %s", cl->hostname, cl->name, target->name, text, canspeech ? "SPAM detected" : "Forbidden speech"); if (canspeech) { sendservmsg("\f3Please do not spam; your message was not delivered.", sender); - if ( cl->spamcount > SPAMMAXREPEAT + 2 ) disconnect_client(cl->clientnum, DISC_ABUSE); + if ( cl->spamcount > SPAMMAXREPEAT + 2 ) disconnect_client(cl->clientnum, DISC_SPAM); } else { @@ -3035,7 +3308,6 @@ void process(ENetPacket *packet, int sender, int chan) if ( cl->spam < 0 ) cl->spam = 0; cl->lastvc = servmillis; // register if ( cl->spam > 4 ) { cl->mute = servmillis + 10000; break; } // 5 vcs in less than 20 seconds... shut up please - if ( m_teammode ) checkteamplay(s,sender); // finally here we check the teamplay comm if ( type == SV_VOICECOM ) { QUEUE_MSG; } else sendvoicecomteam(s, sender); } @@ -3046,17 +3318,17 @@ void process(ENetPacket *packet, int sender, int chan) { int gzs = getint(p); int rev = getint(p); - if(!isdedicated || (smapstats.cgzsize == gzs && smapstats.hdr.maprevision == rev)) + if(!isdedicated || (sg->curmap && sg->curmap->cgzlen == gzs && sg->curmap->maprevision == rev)) { // here any game really starts for a client: spawn, if it's a new game - don't spawn if the game was already running cl->isonrightmap = true; int sp = canspawn(cl); sendf(sender, 1, "rii", SV_SPAWNDENY, sp); cl->spawnperm = sp; - if(cl->loggedwrongmap) logline(ACLOG_INFO, "[%s] %s is now on the right map: revision %d/%d", cl->hostname, cl->name, rev, gzs); + if(cl->loggedwrongmap) mlog(ACLOG_INFO, "[%s] %s is now on the right map: revision %d/%d", cl->hostname, cl->name, rev, gzs); bool spawn = false; if(team_isspect(cl->team)) { - if(numclients() < 2 && !m_demo && mastermode != MM_MATCH) // spawn on empty servers + if(numclients() < 2 && !m_demo && sg->mastermode != MM_MATCH) // spawn on empty servers { spawn = updateclientteam(cl->clientnum, TEAM_ANYACTIVE, FTR_INFO); } @@ -3072,7 +3344,7 @@ void process(ENetPacket *packet, int sender, int chan) else { forcedeath(cl); - logline(ACLOG_INFO, "[%s] %s is on the wrong map: revision %d/%d", cl->hostname, cl->name, rev, gzs); + mlog(ACLOG_INFO, "[%s] %s is on the wrong map: revision %d/%d", cl->hostname, cl->name, rev, gzs); cl->loggedwrongmap = true; sendf(sender, 1, "rii", SV_SPAWNDENY, SP_WRONGMAP); } @@ -3083,7 +3355,7 @@ void process(ENetPacket *packet, int sender, int chan) case SV_ITEMPICKUP: { int n = getint(p); - if(!arenaround || arenaround - gamemillis > 2000) + if(!sg->arenaround || sg->arenaround - sg->gamemillis > 2000) { gameevent &pickup = cl->addevent(); pickup.type = GE_PICKUP; @@ -3095,8 +3367,7 @@ void process(ENetPacket *packet, int sender, int chan) case SV_WEAPCHANGE: { int gunselect = getint(p); - if(!valid_weapon(gunselect) || gunselect == GUN_CPISTOL) break; - if(!m_demo && !m_coop) checkweapon(type,gunselect); + if(!valid_weapon(gunselect)) break; cl->state.gunselect = gunselect; QUEUE_MSG; break; @@ -3118,7 +3389,7 @@ void process(ENetPacket *packet, int sender, int chan) if(!text[0]) copystring(text, "unarmed"); QUEUE_STR(text); bool namechanged = strcmp(cl->name, text) != 0; - if(namechanged) logline(ACLOG_INFO,"[%s] %s changed name to %s", cl->hostname, cl->name, text); + if(namechanged) mlog(ACLOG_INFO,"[%s] %s changed name to %s", cl->hostname, cl->name, text); copystring(cl->name, text, MAXNAMELEN+1); if(namechanged) { @@ -3127,7 +3398,7 @@ void process(ENetPacket *packet, int sender, int chan) { ++cl->fastprofileupdates; if(cl->fastprofileupdates == 3) sendservmsg("\f3Please do not spam", sender); - if(cl->fastprofileupdates >= 5) { disconnect_client(sender, DISC_ABUSE); break; } + if(cl->fastprofileupdates >= 5) { disconnect_client(sender, DISC_SPAM); break; } } else if(servmillis - cl->lastprofileupdate > 10000) cl->fastprofileupdates = 0; cl->lastprofileupdate = servmillis; @@ -3136,7 +3407,7 @@ void process(ENetPacket *packet, int sender, int chan) { case NWL_PWDFAIL: case NWL_IPFAIL: - logline(ACLOG_INFO, "[%s] '%s' matches nickname whitelist: wrong IP/PWD", cl->hostname, cl->name); + mlog(ACLOG_INFO, "[%s] '%s' matches nickname whitelist: wrong IP/PWD", cl->hostname, cl->name); disconnect_client(sender, DISC_BADNICK); break; @@ -3145,12 +3416,13 @@ void process(ENetPacket *packet, int sender, int chan) int l = nickblacklist.checkblacklist(cl->name); if(l >= 0) { - logline(ACLOG_INFO, "[%s] '%s' matches nickname blacklist line %d", cl->hostname, cl->name, l); + mlog(ACLOG_INFO, "[%s] '%s' matches nickname blacklist line %d", cl->hostname, cl->name, l); disconnect_client(sender, DISC_BADNICK); } break; } } + if(cl->vita) cl->vita->addname(cl->name); } break; } @@ -3171,7 +3443,7 @@ void process(ENetPacket *packet, int sender, int chan) { ++cl->fastprofileupdates; if(cl->fastprofileupdates == 3) sendservmsg("\f3Please do not spam", sender); - if(cl->fastprofileupdates >= 5) disconnect_client(sender, DISC_ABUSE); + if(cl->fastprofileupdates >= 5) disconnect_client(sender, DISC_SPAM); } else if(servmillis - cl->lastprofileupdate > 10000) cl->fastprofileupdates = 0; cl->lastprofileupdate = servmillis; @@ -3186,7 +3458,7 @@ void process(ENetPacket *packet, int sender, int chan) updateclientteam(sender, TEAM_ANYACTIVE, FTR_PLAYERWISH); sp = canspawn(cl); } - if( !m_arena && sp < SP_OK_NUM && gamemillis > cl->state.lastspawn + 1000 ) sendspawn(cl); + if( !m_arena && sp < SP_OK_NUM && sg->gamemillis > cl->state.lastspawn + 1000 ) sendspawn(cl); break; } @@ -3194,9 +3466,9 @@ void process(ENetPacket *packet, int sender, int chan) { int ls = getint(p), gunselect = getint(p); if((cl->state.state!=CS_ALIVE && cl->state.state!=CS_DEAD && cl->state.state!=CS_SPECTATE) || - ls!=cl->state.lifesequence || cl->state.lastspawn<0 || !valid_weapon(gunselect) || gunselect == GUN_CPISTOL) break; + ls!=cl->state.lifesequence || cl->state.lastspawn<0 || !valid_weapon(gunselect)) break; cl->state.lastspawn = -1; - cl->state.spawn = gamemillis; + cl->state.spawn = sg->gamemillis; cl->autospawn = false; cl->upspawnp = false; cl->state.state = CS_ALIVE; @@ -3214,6 +3486,11 @@ void process(ENetPacket *packet, int sender, int chan) break; } + case SV_SPECTCN: + cl->spectcn = getint(p); + QUEUE_MSG; // FIXME: demo only + break; + case SV_SUICIDE: { gameevent &suicide = cl->addevent(); @@ -3228,11 +3505,11 @@ void process(ENetPacket *packet, int sender, int chan) #define seteventmillis(event) \ { \ event.id = getint(p); \ - if(!cl->timesync || (cl->events.length()==1 && cl->state.waitexpired(gamemillis))) \ + if(!cl->timesync || (cl->events.length()==1 && cl->state.waitexpired(sg->gamemillis))) \ { \ cl->timesync = true; \ - cl->gameoffset = gamemillis - event.id; \ - event.millis = gamemillis; \ + cl->gameoffset = sg->gamemillis - event.id; \ + event.millis = sg->gamemillis; \ } \ else event.millis = cl->gameoffset + event.id; \ } @@ -3241,21 +3518,19 @@ void process(ENetPacket *packet, int sender, int chan) loopk(3) { shot.shot.from[k] = cl->state.o.v[k] + ( k == 2 ? (((cl->f>>7)&1)?2.2f:4.2f) : 0); } loopk(3) { float v = getint(p)/DMF; shot.shot.to[k] = ((k<2 && v<0.0f)?0.0f:v); } int hits = getint(p); - int tcn = -1; loopk(hits) { gameevent &hit = cl->addevent(); hit.type = GE_HIT; - tcn = hit.hit.target = getint(p); + hit.hit.target = getint(p); hit.hit.lifesequence = getint(p); hit.hit.info = getint(p); loopk(3) hit.hit.dir[k] = getint(p)/DNF; } - if(!m_demo && !m_coop) checkshoot(sender, shot, hits, tcn); break; } - case SV_EXPLODE: // Brahma says: FIXME handle explosion by location and deal damage from server + case SV_EXPLODE: { gameevent &exp = cl->addevent(); exp.type = GE_EXPLODE; @@ -3292,37 +3567,6 @@ void process(ENetPacket *packet, int sender, int chan) break; } - // for AUTH: - - case SV_AUTHTRY: - { - string desc, name; - getstring(desc, p, sizeof(desc)); // unused for now - getstring(name, p, sizeof(name)); - if(!desc[0]) tryauth(cl, name); - break; - } - - case SV_AUTHANS: - { - string desc, ans; - getstring(desc, p, sizeof(desc)); // unused for now - uint id = (uint)getint(p); - getstring(ans, p, sizeof(ans)); - if(!desc[0]) answerchallenge(cl, id, ans); - break; - } - - - case SV_AUTHT: - { -/* int n = getint(p); - loopi(n) getint(p);*/ -// if (cl) disconnect_client(cl->clientnum, DISC_TAGT); // remove this in the future, when auth is complete - break; - } - // :for AUTH - case SV_PING: sendf(sender, 1, "ii", SV_PONG, getint(p)); break; @@ -3346,23 +3590,30 @@ void process(ENetPacket *packet, int sender, int chan) #endif return; } - loopi(3) cl->state.o[i] = getuint(p)/DMF; - cl->y = getuint(p); - cl->p = getint(p); - cl->g = getuint(p); - loopi(4) if ( (cl->g >> i) & 1 ) getint(p); - cl->f = getuint(p); + cl->state.o.x = getuint(p)/DMF; + cl->state.o.y = getuint(p)/DMF; + int z = getuint(p); + uint64_t ff = getuintn(p, (YAWBITS + PITCHBITS + FLAGBITS + 7) / 8); + cl->yaw = (int)decodeyaw((ff >> (FLAGBITS + PITCHBITS)) & ((1 << YAWBITS) - 1)); + cl->pitch = (int)decodepitch((ff >> FLAGBITS) & ((1 << PITCHBITS) - 1)); + cl->f = ff & ((1 << FLAGBITS) - 1); + if(cl->f & (1 << 10)) z = -z; + cl->state.o.z = z/DMF; + if(cl->f & (1 << 9)) loopi(3) getint(p); + if(!cl->isonrightmap) break; if(cl->type==ST_TCPIP && (cl->state.state==CS_ALIVE || cl->state.state==CS_EDITING)) { cl->position.setsize(0); while(curmsgposition.add(p.buf[curmsg++]); } - if(!m_demo && !m_coop) checkmove(cl); break; } case SV_POSC: + case SV_POSC2: + case SV_POSC3: + case SV_POSC4: { bitbuf q(p); int cn = q.getbits(5); @@ -3374,36 +3625,31 @@ void process(ENetPacket *packet, int sender, int chan) #endif return; } - int usefactor = q.getbits(2) + 7; + int usefactor = type - SV_POSC + 7; int xt = q.getbits(usefactor + 4); int yt = q.getbits(usefactor + 4); - cl->y = (q.getbits(9)*360)/512; - cl->p = ((q.getbits(8)-128)*90)/127; - if(!q.getbits(1)) q.getbits(6); - if(!q.getbits(1)) q.getbits(4 + 4 + 4); - cl->f = q.getbits(8); - int negz = q.getbits(1); + cl->yaw = (int)decodeyaw(q.getbits(YAWBITS)); + cl->pitch = (int)decodepitch(q.getbits(PITCHBITS)); + cl->f = q.getbits(FLAGBITS); + if(cl->f & (1 << 9)) q.getbits(4 + 4 + 4); int zfull = q.getbits(1); int s = q.rembits(); if(s < 3) s += 8; if(zfull) s = 11; int zt = q.getbits(s); - if(negz) zt = -zt; - int g1 = q.getbits(1); // scoping - int g2 = q.getbits(1); // shooting - cl->g = (g1<<4) | (g2<<5); + if(cl->f & (1 << 10)) zt = -zt; + if(!sg->curmap) break; // no compact POS packets without loaded servermap if(!cl->isonrightmap || p.remaining() || p.overread()) { p.flags = 0; break; } - if(((cl->f >> 6) & 1) != (cl->state.lifesequence & 1) || usefactor != (smapstats.hdr.sfactor < 7 ? 7 : smapstats.hdr.sfactor)) break; - cl->state.o[0] = xt / DMF; - cl->state.o[1] = yt / DMF; - cl->state.o[2] = zt / DMF; + if(((cl->f >> 6) & 1) != (cl->state.lifesequence & 1) || usefactor != (sg->curmap->sfactor < 7 ? 7 : sg->curmap->sfactor)) break; + cl->state.o.x = xt / DMF; + cl->state.o.y = yt / DMF; + cl->state.o.z = zt / DMF; if(cl->type==ST_TCPIP && (cl->state.state==CS_ALIVE || cl->state.state==CS_EDITING)) { cl->position.setsize(0); while(curmsgposition.add(p.buf[curmsg++]); } - if(!m_demo && !m_coop) checkmove(cl); break; } @@ -3421,42 +3667,56 @@ void process(ENetPacket *packet, int sender, int chan) p.forceoverread(); break; } - int mp = findmappath(sentmap); - if(readonlymap(mp)) + servermap *sm = getservermap(sentmap); + header *h = peekmapheader(&p.buf[p.len], mapsize); + int actualrevision = h ? h->maprevision : 0; + if(sm && sm->isro()) { reject = "map is ro"; defformatstring(msg)("\f3map upload rejected: map %s is readonly", sentmap); sendservmsg(msg, sender); } - else if( scl.incoming_limit && ( scl.incoming_limit << 20 ) < incoming_size + mapsize + cfgsizegz ) + else if(scl.incoming_limit && (scl.incoming_limit << 20) - incoming_size < mapsize + cfgsizegz) { reject = "server incoming reached its limits"; sendservmsg("\f3server does not support more incomings: limit reached", sender); } - else if(mp == MAP_NOTFOUND && strchr(scl.mapperm, 'C') && cl->role < CR_ADMIN) + else if(!sm && strchr(scl.mapperm, 'C') && cl->role < CR_ADMIN) { reject = "no permission for initial upload"; sendservmsg("\f3initial map upload rejected: you need to be admin", sender); } - else if(mp == MAP_TEMP && revision >= mapbuffer.revision && !strchr(scl.mapperm, 'u') && cl->role < CR_ADMIN) // default: only admins can update maps + else if(sm && !sm->isro() && revision >= sm->maprevision && !strchr(scl.mapperm, 'u') && cl->role < CR_ADMIN) // default: only admins can update maps { reject = "no permission to update"; sendservmsg("\f3map update rejected: you need to be admin", sender); } - else if(mp == MAP_TEMP && revision < mapbuffer.revision && !strchr(scl.mapperm, 'r') && cl->role < CR_ADMIN) // default: only admins can revert maps to older revisions + else if(revision != actualrevision) + { + reject = "fake revision number"; + } + else if(sm && !sm->isro() && revision < sm->maprevision && !strchr(scl.mapperm, 'r') && cl->role < CR_ADMIN) // default: only admins can revert maps to older revisions { reject = "no permission to revert revision"; sendservmsg("\f3map revert to older revision rejected: you need to be admin to upload an older map", sender); } else - { - if(mapbuffer.sendmap(sentmap, mapsize, cfgsize, cfgsizegz, &p.buf[p.len])) + { // map accepted + if(serverwritemap(sentmap, mapsize, cfgsize, cfgsizegz, &p.buf[p.len])) { - incoming_size += mapsize + cfgsizegz; - logline(ACLOG_INFO,"[%s] %s sent map %s, rev %d, %d + %d(%d) bytes written", + if(sg->smode == GMODE_COOPEDIT && !strcmp(sentmap, behindpath(sg->smapname))) + { // update buffer only in coopedit mode (and on same map) + sg->coop_cgzlen = mapsize; + sg->coop_cfglen = cfgsize; + sg->coop_cfglengz = cfgsizegz; + DELETEA(sg->coop_mapdata); + sg->coop_mapdata = new uchar[mapsize + cfgsizegz]; + memcpy(sg->coop_mapdata, &p.buf[p.len], mapsize + cfgsizegz); + } + incoming_size += mapsize + cfgsize; + mlog(ACLOG_INFO,"[%s] %s sent map %s, rev %d, %d + %d(%d) bytes written", clients[sender]->hostname, clients[sender]->name, sentmap, revision, mapsize, cfgsize, cfgsizegz); - defformatstring(msg)("%s (%d) up%sed map %s, rev %d%s", clients[sender]->name, sender, mp == MAP_NOTFOUND ? "load": "dat", sentmap, revision, - /*strcmp(sentmap, behindpath(smapname)) || smode == GMODE_COOPEDIT ? "" :*/ "\f3 (restart game to use new map version)"); + defformatstring(msg)("%s (%d) up%sed map %s, rev %d", clients[sender]->name, sender, !sm ? "load": "dat", sentmap, revision); sendservmsg(msg); } else @@ -3467,7 +3727,7 @@ void process(ENetPacket *packet, int sender, int chan) } if (reject) { - logline(ACLOG_INFO,"[%s] %s sent map %s rev %d, rejected: %s", + mlog(ACLOG_INFO,"[%s] %s sent map %s rev %d, rejected: %s", clients[sender]->hostname, clients[sender]->name, sentmap, revision, reject); } p.len += mapsize + cfgsizegz; @@ -3476,16 +3736,22 @@ void process(ENetPacket *packet, int sender, int chan) case SV_RECVMAP: { - if(mapbuffer.available()) + if(sg->smode == GMODE_COOPEDIT && sg->coop_mapdata) // "getmap" only in coopedit { resetflag(cl->clientnum); // drop ctf flag savedscore *sc = findscore(*cl, true); // save score if(sc) sc->save(cl->state, cl->team); - mapbuffer.sendmap(cl, 2); + packetbuf p(MAXTRANS + sg->coop_cgzlen + sg->coop_cfglengz, ENET_PACKET_FLAG_RELIABLE); + putint(p, SV_RECVMAP); + sendstring(sg->smapname, p); + putint(p, sg->coop_cgzlen); + putint(p, sg->coop_cfglen); + putint(p, sg->coop_cfglengz); + p.put(sg->coop_mapdata, sg->coop_cgzlen + sg->coop_cfglengz); + sendpacket(cl->clientnum, 2, p.finalize()); cl->mapchange(true); sendwelcome(cl, 2); // resend state properly } - else sendservmsg("no map to get", cl->clientnum); break; } @@ -3495,11 +3761,11 @@ void process(ENetPacket *packet, int sender, int chan) filtertext(text, text, FTXT__MAPNAME); string filename; const char *rmmap = behindpath(text), *reject = NULL; - int mp = findmappath(rmmap); + servermap *sm = getservermap(rmmap); int reqrole = strchr(scl.mapperm, 'D') ? CR_ADMIN : (strchr(scl.mapperm, 'd') ? CR_DEFAULT : CR_ADMIN + 100); if(cl->role < reqrole) reject = "no permission"; - else if(readonlymap(mp)) reject = "map is readonly"; - else if(mp == MAP_NOTFOUND) reject = "map not found"; + else if(sm && sm->isro()) reject = "map is readonly"; + else if(!sm) reject = "map not found"; else { formatstring(filename)(SERVERMAP_PATH_INCOMING "%s.cgz", rmmap); @@ -3508,11 +3774,11 @@ void process(ENetPacket *packet, int sender, int chan) remove(filename); defformatstring(msg)("map '%s' deleted", rmmap); sendservmsg(msg, sender); - logline(ACLOG_INFO,"[%s] deleted map %s", clients[sender]->hostname, rmmap); + mlog(ACLOG_INFO,"[%s] deleted map %s", clients[sender]->hostname, rmmap); } if (reject) { - logline(ACLOG_INFO,"[%s] deleting map %s failed: %s", clients[sender]->hostname, rmmap, reject); + mlog(ACLOG_INFO,"[%s] deleting map %s failed: %s", clients[sender]->hostname, rmmap, reject); defformatstring(msg)("\f3can't delete map '%s', %s", rmmap, reject); sendservmsg(msg, sender); } @@ -3555,15 +3821,14 @@ void process(ENetPacket *packet, int sender, int chan) else filtertext(text, behindpath(text), FTXT__MAPNAME); if(time <= 0) time = -1; time = min(time, 60); - if (vi->gonext) + if(vi->gonext) { - int ccs = rnd(maprot.configsets.length()); - configset *c = maprot.get(ccs); + servermap *c = maprot.next(false, true); if(c) { - strcpy(vi->text,c->mapname); - mode = vi->num1 = c->mode; - time = vi->num2 = c->time; + strcpy(vi->text,c->fname); + vi->num1 = c->bestmode; + vi->num2 = c->modes[c->bestmode].time; } else fatal("unable to get next map in maprot"); } @@ -3588,7 +3853,6 @@ void process(ENetPacket *packet, int sender, int chan) getstring(text, p); copystring(vi->text,text,128); filtertext(text, text, FTXT__KICKBANREASON); - trimtrailingwhitespace(text); vi->action = new kickaction(cn2boot, newstring(text, 128)); vi->boot = 1; break; @@ -3599,7 +3863,6 @@ void process(ENetPacket *packet, int sender, int chan) getstring(text, p); copystring(vi->text,text,128); filtertext(text, text, FTXT__KICKBANREASON); - trimtrailingwhitespace(text); vi->action = new banaction(cn2boot, newstring(text, 128)); vi->boot = 2; break; @@ -3665,6 +3928,13 @@ void process(ENetPacket *packet, int sender, int chan) senddemo(sender, getint(p)); break; + case SV_DEMOSIGNATURE: // client acknowledging a demo checksum + getint(p); + getip4(p); + p.get((uchar*)text, 64); + QUEUE_MSG; + break; + case SV_EXTENSION: { // AC server extensions @@ -3696,19 +3966,90 @@ void process(ENetPacket *packet, int sender, int chan) getstring(text, p, n); if(valid_client(sender) && clients[sender]->role==CR_ADMIN) { - logline(ACLOG_INFO, "[%s] %s writes to log: %s", cl->hostname, cl->name, text); + mlog(ACLOG_INFO, "[%s] %s writes to log: %s", cl->hostname, cl->name, text); sendservmsg("your message has been logged", sender); } } + else if(!strcmp(ext, "servpar::get")) + { + getstring(text, p, n); + if(valid_client(sender)) + { + filtertext(text, text, FTXT__SERVPARNAME); + servpar *id = servpars->access(text); + string msg; + if(id && id->type == SID_INT) formatstring(msg)("%s = %d", id->name, *id->i); + else if(id && id->type == SID_STR) formatstring(msg)("%s = \"%s\"", id->name, id->s); + else formatstring(msg)("servpar %s not found", text); + sendservmsg(msg, sender); + } + + } + else if(!strcmp(ext, "servpar::set")) + { + getstring(text, p, n); + if(valid_client(sender) && cl->checkvitadate(VS_OWNER)) + { + char *b, *k = strtok_r(text, " :", &b), *v = strtok_r(NULL, " :", &b); + if(k && v) + { + filtertext(k, k, FTXT__SERVPARNAME); + servpar *id = servpars->access(k); + string msg; + if(!id) formatstring(msg)("servpar %s not found", k); + else if(id->nextvalue(v)) + { + serverparameters.busy.wait(); // lock parameter access + id->fromfile = false; + id->update(); + serverparameters.busy.post(); + formatstring(msg)("%s set to %s", id->name, v); + } + else formatstring(msg)("%s cannot be set to \"%s\"", id->name, v); + sendservmsg(msg, sender); + } + } + } + else if(!strcmp(ext, "servpar::reset")) + { + getstring(text, p, n); + if(valid_client(sender) && cl->checkvitadate(VS_OWNER)) + { + enumerate(*servpars, servpar, id, id.fromfile = id.fromfile_org); + serverparameters.filehash = 0; // make sure, the parameter file is read soon + sendservmsg("reset all server parameters to be read from file", sender); + } + + } +#ifdef _DEBUG + else if(!strcmp(ext, "servpar::dump")) + { + getstring(text, p, n); + if(valid_client(sender) && cl->checkvitadate(VS_OWNER)) + { + string msg; + enumerate(*servpars, servpar, id, + { + switch(id.type) + { + case SID_INT: formatstring(msg)("%s = %d, int %d..%d, def %d, %s", id.name, *id.i, id.minval, id.maxval, id.defaultint, id.desc && *id.desc ? id.desc + 1 : ""); break; + case SID_STR: formatstring(msg)("%s = \"%s\", def %s, %s", id.name, id.s, id.defaultstr, id.desc && *id.desc ? id.desc + 1 : ""); break; + default: msg[0] = '\0'; + } + if(*msg) sendservmsg(msg, sender); + }); + } + } +#endif else if(!strcmp(ext, "set::teamsize")) { // intermediate solution to set the teamsize (will be voteable) getstring(text, p, n); - if(valid_client(sender) && clients[sender]->role==CR_ADMIN && mastermode == MM_MATCH) + if(valid_client(sender) && clients[sender]->role==CR_ADMIN && sg->mastermode == MM_MATCH) { changematchteamsize(atoi(text)); - defformatstring(msg)("match team size set to %d", matchteamsize); + defformatstring(msg)("match team size set to %d", sg->matchteamsize); sendservmsg(msg, -1); } } @@ -3762,6 +4103,7 @@ void localclienttoserver(int chan, ENetPacket *packet) client &addclient() { + static int clientsequencecounter = 0; client *c = NULL; loopv(clients) if(clients[i]->type==ST_EMPTY) { c = clients[i]; break; } if(!c) @@ -3771,28 +4113,34 @@ client &addclient() clients.add(c); } c->reset(); + c->clientsequence = ++clientsequencecounter; return *c; } void checkintermission() { - if(minremain>0) + if(sg->minremain>0) { - minremain = (gamemillis>=gamelimit || forceintermission) ? 0 : (gamelimit - gamemillis + 60000 - 1)/60000; - sendf(-1, 1, "ri3", SV_TIMEUP, (gamemillis>=gamelimit || forceintermission) ? gamelimit : gamemillis, gamelimit); + sg->minremain = (sg->gamemillis >= sg->gamelimit || sg->forceintermission) ? 0 : (sg->gamelimit - sg->gamemillis + 60000 - 1) / 60000; + sendf(-1, 1, "ri3", SV_TIMEUP, (sg->gamemillis >= sg->gamelimit || sg->forceintermission) ? sg->gamelimit : sg->gamemillis, sg->gamelimit); + if(demorecord && sg->gamemillis > 100000) sg->demosequence = demorecord->sequence; // after 100 seconds played, switch F10-default from "last game" to "this game" } - if(!interm && minremain<=0) interm = gamemillis+10000; - forceintermission = false; + if(!sg->interm && sg->minremain <= 0) + { // intermission starting here + sg->interm = sg->gamemillis + 10000; + demorecord_beginintermission(); + } + sg->forceintermission = false; } void resetserverifempty() { loopv(clients) if(clients[i]->type!=ST_EMPTY) return; resetserver("", 0, 10); - matchteamsize = 0; - autoteam = true; + sg->matchteamsize = 0; + sg->autoteam = true; changemastermode(MM_OPEN); - nextmapname[0] = '\0'; + sg->nextmapname[0] = '\0'; } void sendworldstate() @@ -3804,28 +4152,19 @@ void sendworldstate() bool flush = buildworldstate(); lastsend += curtime - (curtime%40); if(flush) enet_host_flush(serverhost); - if(demorecord) recordpackets = true; // enable after 'old' worldstate is sent -} - -void rereadcfgs(void) -{ - maprot.read(); - ipblacklist.read(); - nickblacklist.read(); - forbiddenlist.read(); - passwords.read(); + if(demorecord) sg->recordpackets = true; // enable after 'old' worldstate is sent } void loggamestatus(const char *reason) { int fragscore[2] = {0, 0}, flagscore[2] = {0, 0}, pnum[2] = {0, 0}; string text; - formatstring(text)("%d minutes remaining", minremain); - logline(ACLOG_INFO, ""); - logline(ACLOG_INFO, "Game status: %s on %s, %s, %s, %d clients%c %s", - modestr(gamemode), smapname, reason ? reason : text, mmfullname(mastermode), totalclients, custom_servdesc ? ',' : '\0', servdesc_current); + formatstring(text)("%d minutes remaining", sg->minremain); + mlog(ACLOG_INFO, ""); + mlog(ACLOG_INFO, "Game status: %s on %s, %s, %s, %d clients%c %s", + modestr(gamemode), sg->smapname, reason ? reason : text, mmfullname(sg->mastermode), totalclients, sg->custom_servdesc ? ',' : '\0', sg->servdesc_current); if(!scl.loggamestatus) return; - logline(ACLOG_INFO, "cn name %s%s score frag death %sping role host", m_teammode ? "team " : "", m_flags ? "flag " : "", m_teammode ? "tk " : ""); + mlog(ACLOG_INFO, "cn name %s%s score frag death %sping role host", m_teammode ? "team " : "", m_flags ? "flag " : "", m_teammode ? "tk " : ""); loopv(clients) { client &c = *clients[i]; @@ -3836,7 +4175,7 @@ void loggamestatus(const char *reason) concatformatstring(text, "%6d ", c.state.points); // score concatformatstring(text, "%4d %5d", c.state.frags, c.state.deaths); // frag death if(m_teammode) concatformatstring(text, " %2d", c.state.teamkills); // tk - logline(ACLOG_INFO, "%s%5d %s %s", text, c.ping, c.role == CR_ADMIN ? "admin " : "normal", c.hostname); + mlog(ACLOG_INFO, "%s%5d %s %s", text, c.ping, c.role == CR_ADMIN ? "admin " : "normal", c.hostname); if(c.team != TEAM_SPECT) { int t = team_base(c.team); @@ -3845,7 +4184,7 @@ void loggamestatus(const char *reason) pnum[t] += 1; } } - if(mastermode == MM_MATCH) + if(sg->mastermode == MM_MATCH) { loopv(savedscores) { @@ -3855,7 +4194,7 @@ void loggamestatus(const char *reason) if(m_teammode) formatstring(text)("%-4s ", team_string(sc.team, true)); else text[0] = '\0'; if(m_flags) concatformatstring(text, "%4d ", sc.flagscore); - logline(ACLOG_INFO, " %-16s %s%4d %5d%s - disconnected", sc.name, text, sc.frags, sc.deaths, m_teammode ? " -" : ""); + mlog(ACLOG_INFO, " %-16s %s%4d %5d%s - disconnected", sc.name, text, sc.frags, sc.deaths, m_teammode ? " -" : ""); if(sc.team != TEAM_SPECT) { int t = team_base(sc.team); @@ -3868,9 +4207,9 @@ void loggamestatus(const char *reason) } if(m_teammode) { - loopi(2) logline(ACLOG_INFO, "Team %4s:%3d players,%5d frags%c%5d flags", team_string(i), pnum[i], fragscore[i], m_flags ? ',' : '\0', flagscore[i]); + loopi(2) mlog(ACLOG_INFO, "Team %4s:%3d players,%5d frags%c%5d flags", team_string(i), pnum[i], fragscore[i], m_flags ? ',' : '\0', flagscore[i]); } - logline(ACLOG_INFO, ""); + mlog(ACLOG_INFO, ""); } static unsigned char chokelog[MAXCLIENTS + 1] = { 0 }; @@ -3931,10 +4270,12 @@ void linequalitystats(int elapsed) chokelog[i] = (s << 4) + c; concatformatstring(msg, "%02X ", chokelog[i]); } - logline(ACLOG_DEBUG, "%s] +%d", msg, ncs); + mlog(ACLOG_DEBUG, "%s] +%d", msg, ncs); } } +SERVPARLIST(mandatory_auth, 0, 1, 0, endis, "sEnforce IDs for all clients even on unlisted server/LAN game"); + void serverslice(uint timeout) // main server update, called from cube main loop in sp, or dedicated server loop { static int msend = 0, mrec = 0, csend = 0, crec = 0, mnum = 0, cnum = 0; @@ -3945,34 +4286,33 @@ void serverslice(uint timeout) // main server update, called from cube main lo int nextmillis = isdedicated ? (int)enet_time_get() : lastmillis; #endif int diff = nextmillis - servmillis; - gamemillis += diff; + sg->gamemillis += diff; servmillis = nextmillis; - servertime = ((diff + 3 * servertime)>>2); - if (servertime > 40) serverlagged = servmillis; + entropy_add_byte(diff); #ifndef STANDALONE if(m_demo) { readdemo(); extern void silenttimeupdate(int milliscur, int millismax); - silenttimeupdate(gamemillis, gametimemaximum); + silenttimeupdate(sg->gamemillis, gametimemaximum); } #endif - if(minremain>0) + if(sg->minremain > 0) { processevents(); checkitemspawns(diff); bool ktfflagingame = false; if(m_flags) loopi(2) { - sflaginfo &f = sflaginfos[i]; - if(f.state == CTFF_DROPPED && gamemillis-f.lastupdate > (m_ctf ? 30000 : 10000)) flagaction(i, FA_RESET, -1); - if(m_htf && f.state == CTFF_INBASE && gamemillis-f.lastupdate > (smapstats.hasflags ? 10000 : 1000)) + sflaginfo &f = sg->sflaginfos[i]; + if(f.state == CTFF_DROPPED && sg->gamemillis-f.lastupdate > (m_ctf ? 30000 : 10000)) flagaction(i, FA_RESET, -1); + if(m_htf && f.state == CTFF_INBASE && sg->gamemillis-f.lastupdate > (sg->curmap && sg->curmap->entstats.hasflags ? 10000 : 1000)) { htf_forceflag(i); } - if(m_ktf && f.state == CTFF_STOLEN && gamemillis-f.lastupdate > 15000) + if(m_ktf && f.state == CTFF_STOLEN && sg->gamemillis-f.lastupdate > 15000) { flagaction(i, FA_SCORE, -1); } @@ -3981,9 +4321,7 @@ void serverslice(uint timeout) // main server update, called from cube main lo if(m_ktf && !ktfflagingame) flagaction(rnd(2), FA_RESET, -1); // ktf flag watchdog if(m_arena) arenacheck(); else if(m_autospawn) autospawncheck(); -// if(m_lms) lmscheck(); - sendextras(); - if ( scl.afk_limit && mastermode == MM_OPEN && next_afk_check < servmillis && gamemillis > 20 * 1000 ) check_afk(); + if(scl.afk_limit && sg->mastermode == MM_OPEN && next_afk_check < servmillis && sg->gamemillis > 20 * 1000) check_afk(); } if(curvote) @@ -3994,32 +4332,33 @@ void serverslice(uint timeout) // main server update, called from cube main lo int nonlocalclients = numnonlocalclients(); - if(forceintermission || ((smode>1 || (gamemode==0 && nonlocalclients)) && gamemillis-diff>0 && gamemillis/60000!=(gamemillis-diff)/60000)) + if(sg->forceintermission || ((sg->smode > 1 || (gamemode == 0 && nonlocalclients)) && sg->gamemillis-diff > 0 && sg->gamemillis / 60000 != (sg->gamemillis - diff) / 60000)) checkintermission(); if(m_demo && !demoplayback) maprot.restart(); - else if(interm && ( (scl.demo_interm && sending_demo) ? gamemillis>(interm<<1) : gamemillis>interm ) ) + else if(sg->interm && ( (scl.demo_interm) ? sg->gamemillis > (sg->interm<<1) : sg->gamemillis > sg->interm ) ) { - sending_demo = false; loggamestatus("game finished"); if(demorecord) enddemorecord(); - interm = nextsendscore = 0; + sg->interm = sg->nextsendscore = 0; //start next game - if(nextmapname[0]) startgame(nextmapname, nextgamemode); + if(sg->nextmapname[0]) startgame(sg->nextmapname, sg->nextgamemode); else maprot.next(); - nextmapname[0] = '\0'; + sg->nextmapname[0] = '\0'; map_queued = false; } resetserverifempty(); + polldeferredprocessing(); + checkdemotransmissions(); if(!isdedicated) return; // below is network only - poll_serverthreads(); + poll_serverthreads(); // read config and map files in the background, process the results in the main thread - serverms(smode, numclients(), minremain, smapname, servmillis, serverhost->address, &mnum, &msend, &mrec, &cnum, &csend, &crec, SERVER_PROTOCOL_VERSION); + serverms(sg->smode, numclients(), sg->minremain, sg->smapname, servmillis, serverhost->address, &mnum, &msend, &mrec, &cnum, &csend, &crec, SERVER_PROTOCOL_VERSION, sg->servdesc_current, sg->interm); - if(autoteam && m_teammode && !m_arena && !interm && servmillis - lastfillup > 5000 && refillteams()) lastfillup = servmillis; + if(sg->autoteam && m_teammode && !m_arena && !sg->interm && servmillis - sg->lastfillup > 5000 && refillteams()) sg->lastfillup = servmillis; static unsigned int lastThrottleEpoch = 0; if(serverhost->bandwidthThrottleEpoch != lastThrottleEpoch) @@ -4031,11 +4370,12 @@ void serverslice(uint timeout) // main server update, called from cube main lo if(servmillis - laststatus > 60 * 1000) // display bandwidth stats, useful for server ops { laststatus = servmillis; - rereadcfgs(); + maprot.oneminutepassed(nonlocalclients ? sg->smode : -1); + if(sg->curmap && nonlocalclients) sg->curmap->oneminuteplayed(servmillis, sg->smode); if(nonlocalclients || serverhost->totalSentData || serverhost->totalReceivedData) { if(nonlocalclients) loggamestatus(NULL); - logline(ACLOG_INFO, "Status at %s: %d remote clients, %.1f send, %.1f rec (K/sec);" + mlog(ACLOG_INFO, "Status at %s: %d remote clients, %.1f send, %.1f rec (K/sec);" " Ping: #%d|%d|%d; CSL: #%d|%d|%d (bytes)", timestring(true, "%d-%m-%Y %H:%M:%S"), nonlocalclients, serverhost->totalSentData/60.0f/1024, serverhost->totalReceivedData/60.0f/1024, mnum, msend, mrec, cnum, csend, crec); @@ -4043,6 +4383,11 @@ void serverslice(uint timeout) // main server update, called from cube main lo linequalitystats(0); } serverhost->totalSentData = serverhost->totalReceivedData = 0; + loopv(clients) if(clients[i]->type == ST_TCPIP && clients[i]->isauthed && clients[i]->vita) + { + clients[i]->vita->vs[VS_MINUTESCONNECTED]++; + if(clients[i]->isonrightmap && team_isactive(clients[i]->team)) clients[i]->vita->vs[VS_MINUTESACTIVE]++; + } } ENetEvent event; @@ -4054,20 +4399,28 @@ void serverslice(uint timeout) // main server update, called from cube main lo if(enet_host_service(serverhost, &event, timeout) <= 0) break; serviced = true; } + entropy_add_byte(event.type); switch(event.type) { case ENET_EVENT_TYPE_CONNECT: { client &c = addclient(); c.type = ST_TCPIP; + c.needsauth = usemaster || mandatory_auth; c.peer = event.peer; c.peer->data = (void *)(size_t)c.clientnum; c.connectmillis = servmillis; c.state.state = CS_SPECTATE; - c.salt = rnd(0x1000000)*((servmillis%1000)+1); + c.salt = (rnd(0x1000000)*((servmillis%1000)+1)) ^ rnd(0x1000000); char hn[1024]; copystring(c.hostname, (enet_address_get_host_ip(&c.peer->address, hn, sizeof(hn))==0) ? hn : "unknown"); - logline(ACLOG_INFO,"[%s] client connected", c.hostname); + c.ip = ENET_NET_TO_HOST_32(c.peer->address.host); + c.ip_censored = c.ip & ~((1 << CLIENTIPCENSOR) - 1); + const char *gi = geoiplist.check(c.ip); + copystring(c.country, gi ? gi : "--", 3); + filtercountrycode(c.country, c.country); + entropy_add_byte(c.ip); + mlog(ACLOG_INFO,"[%s] client connected (%s)", c.hostname, c.country); sendservinfo(c); totalclients++; break; @@ -4109,7 +4462,7 @@ void cleanupserver() int getpongflags(enet_uint32 ip) { - int flags = mastermode << PONGFLAG_MASTERMODE; + int flags = sg->mastermode << PONGFLAG_MASTERMODE; flags |= scl.serverpassword[0] ? 1 << PONGFLAG_PASSWORD : 0; loopv(bans) if(bans[i].address.host == ip) { flags |= 1 << PONGFLAG_BANNED; break; } flags |= ipblacklist.check(ip) ? 1 << PONGFLAG_BLACKLIST : 0; @@ -4127,22 +4480,25 @@ void extping_namelist(ucharbuf &p) void extping_serverinfo(ucharbuf &pi, ucharbuf &po) { - char lang[3]; - lang[0] = tolower(getint(pi)); lang[1] = tolower(getint(pi)); lang[2] = '\0'; - const char *reslang = lang, *buf = infofiles.getinfo(lang); // try client language - if(!buf) buf = infofiles.getinfo(reslang = "en"); // try english - sendstring(buf ? reslang : "", po); - if(buf) + getint(pi); getint(pi); // dummy read language code + if(strlen(serverinfoinfo.getmsg())) { - for(const char *c = buf; *c && po.remaining() > MAXINFOLINELEN + 10; c += strlen(c) + 1) sendstring(c, po); - sendstring("", po); + sendstring("en", po); + int pos = 0; + string buf; + while(po.remaining() > MAXINFOLINELEN + 10) + { + sendstring(serverinfoinfo.getmsgline(buf, &pos), po); + if(!*buf) break; + } } + else sendstring("", po); } void extping_maprot(ucharbuf &po) { putint(po, CONFIG_MAXPAR); - string text; +/* string text; bool abort = false; loopv(maprot.configsets) { @@ -4153,7 +4509,7 @@ void extping_maprot(ucharbuf &po) sendstring(abort ? "-- list truncated --" : text, po); loopi(CONFIG_MAXPAR) putint(po, c.par[i]); if(abort) break; - } + }*/ sendstring("", po); } @@ -4183,7 +4539,7 @@ void extinfo_statsbuf(ucharbuf &p, int pid, int bpos, ENetSocket &pongsock, ENet if(clients[i]->type != ST_TCPIP) continue; if(pid>-1 && clients[i]->clientnum!=pid) continue; - bool ismatch = mastermode == MM_MATCH; + bool ismatch = sg->mastermode == MM_MATCH; putint(p,EXT_PLAYERSTATS_RESP_STATS); // send player stats following putint(p,clients[i]->clientnum); //add player id putint(p,clients[i]->ping); //Ping @@ -4216,7 +4572,7 @@ void extinfo_teamscorebuf(ucharbuf &p) { putint(p, m_teammode ? EXT_ERROR_NONE : EXT_ERROR); putint(p, gamemode); - putint(p, minremain); // possible TODO: use gamemillis, gamelimit here too? + putint(p, sg->minremain); if(!m_teammode) return; int teamsizes[TEAM_NUM] = { 0 }, fragscores[TEAM_NUM] = { 0 }, flagscores[TEAM_NUM] = { 0 }; @@ -4251,6 +4607,7 @@ void localconnect() c.type = ST_LOCAL; c.role = CR_ADMIN; c.salt = 0; + c.needsauth = false; copystring(c.hostname, "local"); sendservinfo(c); } @@ -4258,100 +4615,105 @@ void localconnect() void processmasterinput(const char *cmd, int cmdlen, const char *args) { -// AUTH WiP - uint id; string val; - if(sscanf(cmd, "failauth %u", &id) == 1) authfailed(id); - else if(sscanf(cmd, "succauth %u", &id) == 1) authsucceeded(id); - else if(sscanf(cmd, "chalauth %u %s", &id, val) == 2) authchallenged(id, val); - else if(!strncmp(cmd, "cleargbans", cmdlen)) cleargbans(); + if(!strncmp(cmd, "cleargbans", cmdlen)) cleargbans(); else if(sscanf(cmd, "addgban %s", val) == 1) addgban(val); } -string server_name = "unarmed server"; - void quitproc(int param) { // this triggers any "atexit"-calls: exit(param == 2 ? EXIT_SUCCESS : EXIT_FAILURE); // 3 is the only reply on Win32 apparently, SIGINT == 2 == Ctrl-C } -void initserver(bool dedicated, int argc, char **argv) +const char *loglevelnames[] = { "debug", "verbose", "info", "warning", "error", "disabled", "" }; + +SERVPARLISTF(logthreshold_console, 0, ACLOG_INFO, 0, loglevelnames, restartlogging, "CConsole log level"); +SERVPARLISTF(logthreshold_syslog, 0, ACLOG_NUM, 0, loglevelnames, restartlogging, "CSyslog log level"); +SERVPARLISTF(logthreshold_file, 0, ACLOG_INFO, 0, loglevelnames, restartlogging, "CLogfile log level"); +SERVSTAT(stat_mainlog_peaklevel, 0, "lMain log peak fill level (0..100%)"); +SERVSTAT(stat_theadlog_peaklevel, 0, "lThreadlog peak fill level (0..100%)"); + +void restartlogging() { - const char *service = NULL; - - for(int i = 1; iargc = argc; svcctrl->argv = argv; - svcctrl->start(); - } - } +void initserver(bool dedicated) +{ + copystring(servpubkey, ""); + if(scl.ssk && hex2bin(ssk, scl.ssk, 32) != 32) scl.ssk = NULL; + ed25519_pubkey_from_private(spk, ssk); + bin2hex(servpubkey, spk, 32); + sg->smapname[0] = '\0'; + servparallfun(); - if ( strlen(scl.servdesc_full) ) global_name = scl.servdesc_full; - else global_name = server_name; - - smapname[0] = '\0'; - - string identity; - if(scl.logident[0]) filtertext(identity, scl.logident, FTXT__LOGIDENT); - else formatstring(identity)("%s#%d", scl.ip[0] ? scl.ip : "local", scl.serverport); - int conthres = scl.verbose > 1 ? ACLOG_DEBUG : (scl.verbose ? ACLOG_VERBOSE : ACLOG_INFO); - if(dedicated && !initlogging(identity, scl.syslogfacility, conthres, scl.filethres, scl.syslogthres, scl.logtimestamp)) - printf("WARNING: logging not started!\n"); - logline(ACLOG_INFO, "logging local AssaultCube server (version %d, protocol %d/%d) now..", AC_VERSION, SERVER_PROTOCOL_VERSION, EXT_VERSION); - - copystring(servdesc_current, scl.servdesc_full); - servermsinit(scl.master ? scl.master : AC_MASTER_URI, scl.ip, CUBE_SERVINFO_PORT(scl.serverport), dedicated); + // init server parameters from commandline + if(scl.demopath) initserverparameter("demo_path", scl.demopath); + if(scl.maxdemos > 0) initserverparameter("demo_max_number", scl.maxdemos); + // start logging + initserverparameter("logthreshold_console", scl.verbose > 1 ? ACLOG_DEBUG : (scl.verbose ? ACLOG_VERBOSE : ACLOG_INFO)); + initserverparameter("logthreshold_syslog", scl.syslogthres); + initserverparameter("logthreshold_file", scl.filethres); if((isdedicated = dedicated)) { + restartlogging(); + if(!logcheck(ACLOG_ERROR)) printf("WARNING: logging not started!\n"); + } + mlog(ACLOG_INFO, "logging local AssaultCube server (version %d, protocol %d/%d) now..", AC_VERSION, SERVER_PROTOCOL_VERSION, EXT_VERSION); + + copystring(sg->servdesc_current, scl.servdesc_full); + servermsinit(dedicated); + + if(isdedicated) + { + serverparameters.init(scl.parfilepath); + if(serverparameters.load()) serverparameters.read(); // do this early, so the parameters are properly set during initialisation ENetAddress address = { ENET_HOST_ANY, (enet_uint16)scl.serverport }; - if(scl.ip[0] && enet_address_set_host(&address, scl.ip)<0) logline(ACLOG_WARNING, "server ip not resolved!"); + if(scl.ip[0] && enet_address_set_host(&address, scl.ip)<0) mlog(ACLOG_WARNING, "server ip not resolved!"); serverhost = enet_host_create(&address, scl.maxclients+1, 3, 0, scl.uprate); if(!serverhost) fatal("could not create server host"); loopi(scl.maxclients) serverhost->peers[i].data = (void *)-1; - maprot.init(scl.maprot); - maprot.next(false, true); // ensure minimum maprot length of '1' + if(scl.ssk) mlog(ACLOG_INFO,"server public key: %s", servpubkey); + formatstring(vitafilename)("%s.cfg", scl.vitabasename); + formatstring(vitafilename_backup)("%s_bak.cfg", scl.vitabasename); + formatstring(vitafilename_update)("%s_update.cfg", scl.vitabasename); + formatstring(vitafilename_update_backup_base)("%s_update_", scl.vitabasename); + path(vitafilename); path(vitafilename_backup); path(vitafilename_update); path(vitafilename_update_backup_base); + char *vn; + int gotvitas = readvitas((vn = vitafilename)); + if(gotvitas < 0) gotvitas = readvitas((vn = vitafilename_backup)); + if(gotvitas >= 0) mlog(ACLOG_INFO, "read %d player vitas from %s", gotvitas, vn); + maprot.init(scl.maprotfile); passwords.init(scl.pwdfile, scl.adminpasswd); ipblacklist.init(scl.blfile); + geoiplist.init(scl.geoipfile); nickblacklist.init(scl.nbfile); forbiddenlist.init(scl.forbidden); - infofiles.init(scl.infopath, scl.motdpath); - infofiles.getinfo("en"); // cache 'en' serverinfo - logline(ACLOG_VERBOSE, "holding up to %d recorded demos in memory", scl.maxdemos); - if(scl.demopath[0]) logline(ACLOG_VERBOSE,"all recorded demos will be written to: \"%s\"", scl.demopath); - if(scl.voteperm[0]) logline(ACLOG_VERBOSE,"vote permission string: \"%s\"", scl.voteperm); - if(scl.mapperm[0]) logline(ACLOG_VERBOSE,"map permission string: \"%s\"", scl.mapperm); - logline(ACLOG_VERBOSE,"server description: \"%s\"", scl.servdesc_full); - if(scl.servdesc_pre[0] || scl.servdesc_suf[0]) logline(ACLOG_VERBOSE,"custom server description: \"%sCUSTOMPART%s\"", scl.servdesc_pre, scl.servdesc_suf); - logline(ACLOG_VERBOSE,"maxclients: %d, kick threshold: %d, ban threshold: %d", scl.maxclients, scl.kickthreshold, scl.banthreshold); - if(scl.master) logline(ACLOG_VERBOSE,"master server URL: \"%s\"", scl.master); - if(scl.serverpassword[0]) logline(ACLOG_VERBOSE,"server password: \"%s\"", hiddenpwd(scl.serverpassword)); + serverinfoinfo.init(scl.infopath, MAXTRANS / 2); + serverinfomotd.init(scl.motdpath, MAXSTRLEN - 5); + mlog(ACLOG_VERBOSE, "holding up to %d recorded demos in memory", demo_max_number); + if(demo_save) mlog(ACLOG_VERBOSE,"all recorded demos will be written to: \"%s\"", demo_path); + if(scl.voteperm[0]) mlog(ACLOG_VERBOSE,"vote permission string: \"%s\"", scl.voteperm); + if(scl.mapperm[0]) mlog(ACLOG_VERBOSE,"map permission string: \"%s\"", scl.mapperm); + mlog(ACLOG_VERBOSE,"server description: \"%s\"", scl.servdesc_full); + if(scl.servdesc_pre[0] || scl.servdesc_suf[0]) mlog(ACLOG_VERBOSE,"custom server description: \"%sCUSTOMPART%s\"", scl.servdesc_pre, scl.servdesc_suf); + mlog(ACLOG_VERBOSE,"maxclients: %d, kick threshold: %d, ban threshold: %d", scl.maxclients, scl.kickthreshold, scl.banthreshold); + if(scl.master) mlog(ACLOG_VERBOSE,"master server URL: \"%s\"", scl.master); + if(scl.serverpassword[0]) mlog(ACLOG_VERBOSE,"server password: \"%s\"", hiddenpwd(scl.serverpassword)); #ifdef ACAC - logline(ACLOG_INFO, "anticheat: enabled"); + mlog(ACLOG_INFO, "anticheat: enabled"); #else - logline(ACLOG_INFO, "anticheat: disabled"); + mlog(ACLOG_INFO, "anticheat: disabled"); #endif } @@ -4363,24 +4725,30 @@ void initserver(bool dedicated, int argc, char **argv) SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS); #endif // kill -2 / Ctrl-C - see http://msdn.microsoft.com/en-us/library/xdkz3x12%28v=VS.100%29.aspx (or VS-2008?) for caveat (seems not to pertain to AC - 2011feb05:ft) - if (signal(SIGINT, quitproc) == SIG_ERR) logline(ACLOG_INFO, "Cannot handle SIGINT!"); + if (signal(SIGINT, quitproc) == SIG_ERR) mlog(ACLOG_INFO, "Cannot handle SIGINT!"); // kill -15 / probably process-manager on Win32 *shrug* - if (signal(SIGTERM, quitproc) == SIG_ERR) logline(ACLOG_INFO, "Cannot handle SIGTERM!"); + if (signal(SIGTERM, quitproc) == SIG_ERR) mlog(ACLOG_INFO, "Cannot handle SIGTERM!"); #ifndef WIN32 // kill -1 - if (signal(SIGHUP, quitproc) == SIG_ERR) logline(ACLOG_INFO, "Cannot handle SIGHUP!"); - // kill -9 is uncatchable - http://en.wikipedia.org/wiki/SIGKILL - //if (signal(SIGKILL, quitproc) == SIG_ERR) logline(ACLOG_INFO, "Cannot handle SIGKILL!"); + if (signal(SIGHUP, quitproc) == SIG_ERR) mlog(ACLOG_INFO, "Cannot handle SIGHUP!"); #endif - logline(ACLOG_INFO, "dedicated server started, waiting for clients..."); - logline(ACLOG_INFO, "Ctrl-C to exit"); // this will now actually call the atexit-hooks below - thanks to SIGINT hooked above - noticed and signal-code-docs found by SKB:2011feb05:ft: + mlog(ACLOG_INFO, "dedicated server started, waiting for clients..."); + mlog(ACLOG_INFO, "Ctrl-C to exit"); // this will now actually call the atexit-hooks below - thanks to SIGINT hooked above - noticed and signal-code-docs found by SKB:2011feb05:ft: atexit(enet_deinitialize); atexit(cleanupserver); enet_time_set(0); // start file-IO threads readmapsthread_sem = new sl_semaphore(0, NULL); - sl_createthread(readmapsthread, (void *)"xxxx"); + defformatstring(readmapslogfilename)("%sreadmaps_log.txt", scl.logfilepath); + sl_createthread(readmapsthread, (void *)path(readmapslogfilename)); + defformatstring(_modeinfologfilename)("%smisc/modeinfo.txt", scl.logfilepath); + modeinfologfilename = path(_modeinfologfilename); + + readserverconfigsthread_sem = new sl_semaphore(0, NULL); + sl_createthread(readserverconfigsthread, NULL); + + sl_createthread(demoworkerthread, NULL); for(;;) serverslice(5); } @@ -4393,8 +4761,8 @@ void fatal(const char *s, ...) { defvformatstring(msg,s,s); defformatstring(out)("AssaultCube fatal error: %s", msg); - if (logline(ACLOG_ERROR, "%s", out)); - else puts(out); + xlog(ACLOG_ERROR, "%s", out); + puts(out); cleanupserver(); exit(EXIT_FAILURE); } @@ -4410,13 +4778,31 @@ int main(int argc, char **argv) #endif #endif - for(int i = 1; istart(); } - if(enet_initialize()<0) fatal("Unable to initialise network module"); - initserver(true, argc, argv); + if(enet_initialize() < 0) fatal("Unable to initialise network module"); + initserver(true); return EXIT_SUCCESS; #if defined(WIN32) && !defined(_DEBUG) && !defined(__GNUC__) diff --git a/source/src/server.h b/source/src/server.h index eb599be5..e24d5d3f 100644 --- a/source/src/server.h +++ b/source/src/server.h @@ -1,6 +1,6 @@ // server.h -#define gamemode smode // allows the gamemode macros to work with the server mode +#define gamemode sg->smode // allows the gamemode macros to work with the server mode #define SERVER_PROTOCOL_VERSION (PROTOCOL_VERSION) // server without any gameplay modification //#define SERVER_PROTOCOL_VERSION (-PROTOCOL_VERSION) // server with gameplay modification but compatible to vanilla client (using /modconnect) @@ -11,7 +11,7 @@ enum { GE_NONE = 0, GE_SHOT, GE_EXPLODE, GE_HIT, GE_AKIMBO, GE_RELOAD, GE_SUICIDE, GE_PICKUP }; enum { ST_EMPTY, ST_LOCAL, ST_TCPIP }; -extern int smode, servmillis; +extern int servmillis; struct shotevent { @@ -203,43 +203,53 @@ struct savedscore } }; -struct medals +#define VITANAMEHISTLEN 4 +#define VITAIPHISTLEN 4 +#define VITACCHISTLEN 4 +#define VITACOMMENTLEN 31 +#define VITACLANLEN 7 + +struct vita_s { - int dpt, lasthit, lastgun, ncovers, nhs; - int combohits, combo, combofrags, combotime, combodamage, ncombos; - int ask, askmillis, linked, linkmillis, linkreason, upmillis, flagmillis; - int totalhits, totalshots; - bool updated, combosend; - vec pos, flagpos; - void reset() - { - dpt = lasthit = lastgun = ncovers = nhs = 0; - combohits = combo = combofrags = combotime = combodamage = ncombos = 0; - askmillis = linkmillis = upmillis = flagmillis = 0; - linkreason = linked = ask = -1; - totalhits = totalshots = 0; - updated = combosend = false; - pos = flagpos = vec(-1e10f, -1e10f, -1e10f); - } + char namehist[VITANAMEHISTLEN][MAXNAMELEN + 1]; + enet_uint32 iphist[VITAIPHISTLEN]; + char privatecomment[VITACOMMENTLEN + 1], publiccomment[VITACOMMENTLEN + 1]; // private comments can be seen by admins and owners only, public comments can be seen by everyone + char clan[VITACLANLEN + 1]; + char cchist[VITACCHISTLEN * 2]; + int vs[VS_NUM]; + void addname(const char *name); + void addip(enet_uint32 ip); + void addcc(const char *cc); }; +struct vitakey_s { const vita_s *v; const uchar32 *k; }; + struct client // server side version of "dynent" type { int type; - int clientnum; + int clientnum, clientsequence; ENetPeer *peer; string hostname; + enet_uint32 ip, ip_censored; + int connectclock; + char country[4]; + uchar32 pubkey; + char pubkeyhex[68]; + vita_s *vita; + string servinforesponse; string name; int team; + int maxroll, maxrolleffect, ffov, scopefov; char lang[3]; int ping; int skin[2]; int vote; int role; - int connectmillis, lmillis, ldt, spj; + int connectmillis, ldt, spj; int mute, spam, lastvc; // server side voice comm spam control int acversion, acbuildtype; bool isauthed; // for passworded servers + bool needsauth; bool haswelcome; bool isonrightmap, loggedwrongmap, freshgame; bool timesync; @@ -259,25 +269,15 @@ struct client // server side version of "dynent" type bool autospawn; int salt; string pwd; - uint authreq; // for AUTH - string authname; // for AUTH + int spectcn; int mapcollisions, farpickups; enet_uint32 bottomRTT; - medals md; bool upspawnp; int lag; vec spawnp; int nvotes; int input, inputmillis; - int ffire, wn, f, g, t, y, p; - int yb, pb, oy, op, lda, ldda, fam; - int nt[10], np, lp, ls, lsm, ld, nd, nlt, lem, led; - vec cp[10], dp[10], d0, lv, lt, le; - float dda, tr, sda; - int ps, ph, tcn, bdt, pws; - float pr; - int yls, pls, tls; - int bs, bt, blg, bp; + int ffire, wn, f, t, yaw, pitch; gameevent &addevent() { @@ -292,7 +292,7 @@ struct client // server side version of "dynent" type events.setsize(0); overflow = 0; timesync = false; - isonrightmap = m_coop; + isonrightmap = false; spawnperm = SP_WRONGMAP; spawnpermsent = servmillis; autospawn = false; @@ -304,29 +304,23 @@ struct client // server side version of "dynent" type lastevent = 0; at3_lastforce = eff_score = 0; mapcollisions = farpickups = 0; - md.reset(); upspawnp = false; lag = 0; spawnp = vec(-1e10f, -1e10f, -1e10f); - lmillis = ldt = spj = 0; + ldt = spj = 0; ffire = 0; - f = g = y = p = t = 0; - yb = pb = oy = op = lda = ldda = fam = 0; - np = lp = ls = lsm = ld = nd = nlt = lem = led = 0; - d0 = lv = lt = le = vec(0,0,0); - loopi(10) { cp[i] = dp[i] = vec(0,0,0); nt[i] = 0; } - dda = tr = sda = 0; - ps = ph = bdt = pws = 0; - tcn = -1; - pr = 0.0f; - yls = pls = tls = 0; + f = yaw = pitch = t = 0; } void reset() { name[0] = pwd[0] = demoflags = 0; + ip = ip_censored = 0; + pubkeyhex[0] = '\0'; + vita = NULL; bottomRTT = ping = 9999; team = TEAM_SPECT; + maxroll = maxrolleffect = ffov = scopefov = 0; state.state = CS_SPECTATE; loopi(2) skin[i] = 0; position.setsize(0); @@ -339,13 +333,10 @@ struct client // server side version of "dynent" type lastsaytext[0] = '\0'; saychars = 0; spawnindex = -1; - authreq = 0; // for AUTH mapchange(); freshgame = false; // don't spawn into running games mute = spam = lastvc = badspeech = badmillis = nvotes = 0; input = inputmillis = 0; - wn = -1; - bs = bt = blg = bp = 0; } void zap() @@ -354,6 +345,12 @@ struct client // server side version of "dynent" type role = CR_DEFAULT; isauthed = haswelcome = false; } + + bool checkvitadate(int n) + { + extern int servclock; + return vita && (vita->vs[n] == 1 || (servclock - vita->vs[n]) < 0); + } }; struct ban @@ -376,28 +373,28 @@ struct server_entity // server side version of "entity" type short x, y; }; +struct sflaginfo +{ + int state; + int actor_cn; + float pos[3]; + int lastupdate; + int stolentime; + short x, y; // flag entity location +}; + struct clientidentity { uint ip; int clientnum; }; -struct demofile -{ - string info; - string file; - uchar *data; - int len; - vector clientssent; -}; - void startgame(const char *newname, int newmode, int newtime = -1, bool notify = true); void disconnect_client(int n, int reason = -1); void sendiplist(int receiver, int cn = -1); int clienthasflag(int cn); bool refillteams(bool now = false, int ftr = FTR_AUTOTEAM); void changeclientrole(int client, int role, char *pwd = NULL, bool force=false); -mapstats *getservermapstats(const char *mapname, bool getlayout = false, int *maploc = NULL); int findmappath(const char *mapname, char *filename = NULL); int calcscores(); void recordpacket(int chan, void *data, int len); @@ -412,35 +409,35 @@ void forcedeath(client *cl); void sendf(int cn, int chan, const char *format, ...); extern bool isdedicated; -extern string smapname; -extern mapstats smapstats; -extern char *maplayout; + +const char *modeinfologfilename = NULL; const char *messagenames[SV_NUM] = { - "SV_SERVINFO", "SV_WELCOME", "SV_INITCLIENT", "SV_POS", "SV_POSC", "SV_POSN", "SV_TEXT", "SV_TEAMTEXT", "SV_TEXTME", "SV_TEAMTEXTME", "SV_TEXTPRIVATE", + "SV_SERVINFO", "SV_SERVINFO_RESPONSE", "SV_SERVINFO_CONTD", "SV_WELCOME", "SV_INITCLIENT", "SV_POS", "SV_POSC", "SV_POSC2", "SV_POSC3", "SV_POSC4", "SV_POSN", + "SV_TEXT", "SV_TEAMTEXT", "SV_TEXTME", "SV_TEAMTEXTME", "SV_TEXTPRIVATE", "SV_SOUND", "SV_VOICECOM", "SV_VOICECOMTEAM", "SV_CDIS", - "SV_SHOOT", "SV_EXPLODE", "SV_SUICIDE", "SV_AKIMBO", "SV_RELOAD", "SV_AUTHT", "SV_AUTHREQ", "SV_AUTHTRY", "SV_AUTHANS", "SV_AUTHCHAL", + "SV_SHOOT", "SV_EXPLODE", "SV_SUICIDE", "SV_AKIMBO", "SV_RELOAD", "SV_GIBDIED", "SV_DIED", "SV_GIBDAMAGE", "SV_DAMAGE", "SV_HITPUSH", "SV_SHOTFX", "SV_THROWNADE", "SV_TRYSPAWN", "SV_SPAWNSTATE", "SV_SPAWN", "SV_SPAWNDENY", "SV_FORCEDEATH", "SV_RESUME", "SV_DISCSCORES", "SV_TIMEUP", "SV_EDITENT", "SV_ITEMACC", "SV_MAPCHANGE", "SV_ITEMSPAWN", "SV_ITEMPICKUP", - "SV_PING", "SV_PONG", "SV_CLIENTPING", "SV_GAMEMODE", + "SV_PING", "SV_PONG", "SV_CLIENTPING", "SV_EDITMODE", "SV_EDITXY", "SV_EDITARCH", "SV_EDITBLOCK", "SV_EDITD", "SV_EDITE", "SV_NEWMAP", "SV_SENDMAP", "SV_RECVMAP", "SV_REMOVEMAP", - "SV_SERVMSG", "SV_ITEMLIST", "SV_WEAPCHANGE", "SV_PRIMARYWEAP", + "SV_SERVMSG", "SV_SERVMSGVERB", "SV_ITEMLIST", "SV_WEAPCHANGE", "SV_PRIMARYWEAP", "SV_FLAGACTION", "SV_FLAGINFO", "SV_FLAGMSG", "SV_FLAGCNT", "SV_ARENAWIN", "SV_SETADMIN", "SV_SERVOPINFO", "SV_CALLVOTE", "SV_CALLVOTESUC", "SV_CALLVOTEERR", "SV_VOTE", "SV_VOTERESULT", "SV_SETTEAM", "SV_TEAMDENY", "SV_SERVERMODE", - "SV_IPLIST", + "SV_IPLIST", "SV_SPECTCN", "SV_LISTDEMOS", "SV_SENDDEMOLIST", "SV_GETDEMO", "SV_SENDDEMO", "SV_DEMOPLAYBACK", "SV_CONNECT", "SV_SWITCHNAME", "SV_SWITCHSKIN", "SV_SWITCHTEAM", "SV_CLIENT", "SV_EXTENSION", - "SV_MAPIDENT", "SV_HUDEXTRAS", "SV_POINTS" + "SV_MAPIDENT", "SV_DEMOCHECKSUM", "SV_DEMOSIGNATURE", }; const char *entnames[] = @@ -507,7 +504,6 @@ itemstat ammostats[NUMGUNS] = { 60, 90, 90, S_ITEMAMMO }, // subgun { 10, 20, 15, S_ITEMAMMO }, // sniper { 40, 60, 60, S_ITEMAMMO }, // assault - { 30, 45, 75, S_ITEMAMMO }, // cpistol { 1, 0, 3, S_ITEMAMMO }, // grenade {100, 0, 100, S_ITEMAKIMBO} // akimbo }; @@ -534,9 +530,8 @@ guninfo guns[NUMGUNS] = { "subgun", "A-ARD/10 SMG", S_SUBGUN, S_RSUBGUN, 1650, 80, 16, 0, 0, 0, 45, 15, 30, 1, 2, 5, 25, 50, 188, 1, true }, { "sniper", "AD-81 SR", S_SNIPER, S_RSNIPER, 1950, 1500, 82, 25, 0, 0, 50, 50, 5, 4, 4, 10, 85, 85, 100, 1, false }, { "assault", "MTP-57 AR", S_ASSAULT, S_RASSAULT, 2000, 120, 22, 0, 0, 0, 18, 30, 20, 0, 2, 3, 25, 50, 115, 1, true }, - { "cpistol", "nop", S_PISTOL, S_RPISTOL, 1400, 120, 19, 0, 0, 0, 35, 10, 15, 6, 5, 6, 35, 50, 125, 1, false }, // temporary { "grenade", "Grenades", S_NULL, S_NULL, 1000, 650, 200, 0, 20, 6, 1, 1, 1, 3, 1, 0, 0, 0, 0, 3, false }, - { "pistol", "Akimbo", S_PISTOL, S_RAKIMBO, 1400, 80, 19, 0, 0, 0, 50, 10, 20, 6, 5, 4, 15, 25, 115, 1, true }, + { "pistol", "Akimbo", S_PISTOL, S_RAKIMBO, 1400, 80, 18, 0, 0, 0, 50, 10, 20, 6, 5, 4, 15, 25, 115, 1, true }, }; const char *gunnames[NUMGUNS + 1]; @@ -544,10 +539,12 @@ const char *gunnames[NUMGUNS + 1]; const char *teamnames[] = {"CLA", "RVSF", "CLA-SPECT", "RVSF-SPECT", "SPECTATOR", "", "void"}; const char *teamnames_s[] = {"CLA", "RVSF", "CSPC", "RSPC", "SPEC", "", "void"}; +const char *rolenames[CR_NUM + 1] = { "unarmed", "master", "admin", "owner", "" }; + const char *killmessages[2][NUMGUNS] = { - { "", "busted", "picked off", "peppered", "sprayed", "punctured", "shredded", "busted", "", "busted" }, - { "slashed", "", "", "splattered", "", "headshot", "", "", "gibbed", "" } + { "", "busted", "picked off", "peppered", "sprayed", "punctured", "shredded", "", "busted" }, + { "slashed", "", "", "splattered", "", "headshot", "", "gibbed", "" } }; #define C(x) (1<<(SC_##x)) diff --git a/source/src/serveractions.h b/source/src/serveractions.h index 0f04a1a6..c8367b3b 100644 --- a/source/src/serveractions.h +++ b/source/src/serveractions.h @@ -30,7 +30,7 @@ void kick_abuser(int cn, int &cmillis, int &count, int limit) if ( count <= 0 ) count = 1; } cmillis = servmillis; - if( count >= limit ) disconnect_client(cn, DISC_ABUSE); + if( count >= limit ) disconnect_client(cn, DISC_SPAM); } struct mapaction : serveraction @@ -42,14 +42,14 @@ struct mapaction : serveraction { if(queue) { - nextgamemode = mode; - copystring(nextmapname, map); + sg->nextgamemode = mode; + copystring(sg->nextmapname, map); } - else if(isdedicated && numclients() > 2 && smode >= 0 && smode != 1 && ( gamemillis > gamelimit/4 || scl.demo_interm )) + else if(isdedicated && numclients() > 2 && sg->smode >= 0 && sg->smode != 1 && ( sg->gamemillis > sg->gamelimit/4 || scl.demo_interm )) { - forceintermission = true; - nextgamemode = mode; - copystring(nextmapname, map); + sg->forceintermission = true; + sg->nextgamemode = mode; + copystring(sg->nextmapname, map); } else { @@ -57,38 +57,30 @@ struct mapaction : serveraction } } bool isvalid() { return serveraction::isvalid() && mode != GMODE_DEMO && map[0] && mapok && !(isdedicated && !m_mp(mode)); } - bool isdisabled() { return maprot.current() && !maprot.current()->vote; } + bool isdisabled() { return false /*maprot.current() && !maprot.current()->vote*/; } mapaction(char *map, int mode, int time, int caller, bool q) : map(map), mode(mode), time(time), queue(q) { if(isdedicated) { bool notify = valid_client(caller); - int maploc = MAP_NOTFOUND; - mapstats *ms = map[0] ? getservermapstats(map, false, &maploc) : NULL; + servermap *sm = getservermap(map); bool validname = validmapname(map); - mapok = (ms != NULL) && validname && ( (mode != GMODE_COOPEDIT && mapisok(ms)) || (mode == GMODE_COOPEDIT && !readonlymap(maploc)) ); + mapok = sm && validname; if(!mapok) { if(notify) { - if(!validname) - sendservmsg("invalid map name", caller); - else - { - sendservmsg(ms ? - ( mode == GMODE_COOPEDIT ? "this map cannot be coopedited in this server" : "sorry, but this map does not satisfy some quality requisites to be played in MultiPlayer Mode" ) : - "the server does not have this map", - caller); - } + if(!validname) sendservmsg("invalid map name", caller); + else sendservmsg("the server does not have this map", caller); } } else { // check, if map supports mode if(mode == GMODE_COOPEDIT && !strchr(scl.voteperm, 'e')) role = CR_ADMIN; - bool romap = mode == GMODE_COOPEDIT && readonlymap(maploc); - int smode = mode; // 'borrow' the mode macros by replacing a global by a local var - bool spawns = mode == GMODE_COOPEDIT || (m_teammode && !m_ktf ? ms->hasteamspawns : ms->hasffaspawns); - bool flags = mode != GMODE_COOPEDIT && m_flags && !m_htf ? ms->hasflags : true; + bool romap = mode == GMODE_COOPEDIT && sm->isro(); + int gmmask = 1 << mode; + bool spawns = mode == GMODE_COOPEDIT || ((gmmask & GMMASK__TEAMSPAWN) ? sm->entstats.hasteamspawns : sm->entstats.hasffaspawns); + bool flags = (gmmask & GMMASK__FLAGENTS) ? sm->entstats.hasflags : true; if(!spawns || !flags || romap) { // unsupported mode if(strchr(scl.voteperm, 'P')) role = CR_ADMIN; @@ -103,7 +95,7 @@ struct mapaction : serveraction concatstring(msg, " missing"); } if(notify) sendservmsg(msg, caller); - logline(ACLOG_INFO, "%s", msg); + xlog(ACLOG_INFO, "%s", msg); } } loopv(scl.adminonlymaps) @@ -130,10 +122,9 @@ struct mapaction : serveraction } } else - { - int mp = findmappath(map); - mapok = mp == MAP_LOCAL || mp == MAP_OFFICIAL; // clients can only load from packages/maps and packages/maps/official + { // local server #ifndef STANDALONE + mapok = checklocalmap(map) != NULL; // clients can only load from packages/maps and packages/maps/official if(!mapok) conoutf("\f3map '%s' not found", map); #endif } @@ -197,7 +188,7 @@ struct giveadminaction : playeraction struct kickaction : playeraction { bool wasvalid; - void perform() { disconnect(DISC_MKICK); } + void perform() { disconnect(DISC_VOTEKICK); } virtual bool isvalid() { return wasvalid || playeraction::isvalid(); } kickaction(int cn, char *reason) : playeraction(cn) { @@ -217,7 +208,7 @@ struct banaction : playeraction void perform() { int i = findcnbyaddress(&address); - if(i >= 0) addban(clients[i], DISC_MBAN, BAN_VOTE); + if(i >= 0) addban(clients[i], DISC_VOTEBAN, BAN_VOTE); } virtual bool isvalid() { return wasvalid || playeraction::isvalid(); } banaction(int cn, char *reason) : playeraction(cn) @@ -264,7 +255,7 @@ struct autoteamaction : enableaction { void perform() { - autoteam = enable; + sg->autoteam = enable; sendservermode(); if(m_teammode && enable) refillteams(true); } diff --git a/source/src/serverbrowser.cpp b/source/src/serverbrowser.cpp index 166d2115..e239dd44 100644 --- a/source/src/serverbrowser.cpp +++ b/source/src/serverbrowser.cpp @@ -291,7 +291,6 @@ serverinfo *findserverinfo(ENetAddress address) serverinfo *getconnectedserverinfo() { - extern ENetPeer *curpeer; if(!curpeer) return NULL; return findserverinfo(curpeer->address); } @@ -1041,7 +1040,7 @@ void refreshservers(void *menu, bool init) { serverinfo &si = *servers[i]; si.menuline_to = si.menuline_from = ((gmenu *)menu)->items.length(); - if( (!showallservers && si.lastpingmillis <= servermenumillis) || (si.maxclients>MAXCL && searchlan<2) ) continue; // no pong yet or forbidden + if((!showallservers && si.lastpingmillis <= servermenumillis) || (si.maxclients > MAXCLIENTSONMASTER && searchlan<2) ) continue; // no pong yet or forbidden int banned = ((si.pongflags >> PONGFLAG_BANNED) & 1) | ((si.pongflags >> (PONGFLAG_BLACKLIST - 1)) & 2); bool showthisone = !(banned && showonlygoodservers) && !(showonlyfavourites > 0 && si.favcat != showonlyfavourites - 1); bool serverfull = si.numplayers >= si.maxclients; @@ -1304,9 +1303,6 @@ void clearservers() #define RETRIEVELIMIT 5000 -extern char *global_name; -bool cllock = false, clfail = false; - int progress_callback_retrieveservers(void *data, float progress) { if(progress < 0) show_out_of_renderloop_progress(progress + 1.0f, "waiting for response (esc to abort)"); @@ -1328,19 +1324,17 @@ void retrieveservers(vector &data) if(h.set_host(mastername)) { formatstring(progresstext)("retrieving servers from %s:%d... (esc to abort)", mastername, masterport); - defformatstring(url)("/retrieve.do?action=list&name=%s&version=%d&build=%d", urlencode(global_name, true), AC_VERSION, getbuildtype()|(1<<16)); + defformatstring(url)("/retrieve.do?action=list&name=%s&version=%d&build=%d", urlencode(player1->name, true), AC_VERSION, getbuildtype()|(1<<16)); h.outvec = (vector *) &data; // ouch... show_out_of_renderloop_progress(0, progresstext); int got = h.get(url, RETRIEVELIMIT, RETRIEVELIMIT); if(got < 0 || h.response != 200) data.setsize(0); h.outvec = NULL; // must not be cleaned up by httpget if(data.length()) data.add('\0'); - clfail = false; } else { conoutf("failed to resolve host %s", mastername); - clfail = true; } } else @@ -1349,14 +1343,12 @@ void retrieveservers(vector &data) if(sock == ENET_SOCKET_NULL) { conoutf("Master server is not replying."); - clfail = true; return; } - clfail = false; defformatstring(text)("retrieving servers from %s:%d... (esc to abort)", mastername, masterport); show_out_of_renderloop_progress(0, text); int starttime = SDL_GetTicks(), timeout = 0; - defformatstring(request)("list %s %d %d\n", global_name, AC_VERSION, getbuildtype()); + defformatstring(request)("list %s %d %d\n", player1->name, AC_VERSION, getbuildtype()); const char *req = request; int reqlen = strlen(req); ENetBuffer buf; @@ -1406,17 +1398,12 @@ VAR(msctrl, 1, 1, INT_MAX); void updatefrommaster(int *force) { static int lastupdate = 0; - if(lastupdate==0) cllock = true; if(!*force && lastupdate && totalmillis-lastupdate data; retrieveservers(data); - if(data.empty()) - { - if (!clfail) conoutf("Master server is not replying. \f1Get more information at http://masterserver.cubers.net/"); - cllock = !clfail; - } + if(data.empty()) conoutf("Master server is not replying. \f1Get more information at http://masterserver.cubers.net/"); else { // preserve currently connected server from deletion @@ -1425,12 +1412,10 @@ void updatefrommaster(int *force) if(curserver) copystring(curname, curserver->name); clearservers(); - if(!strncmp(data.getbuf(), "addserver", 9)) cllock = false; // the ms could reply other thing... but currently, this is useless - if(!cllock ) - { - execute(data.getbuf()); - if(curserver) addserver(curname, curserver->port, curserver->msweight); - } + + execute(data.getbuf()); + if(curserver) addserver(curname, curserver->port, curserver->msweight); + lastupdate = totalmillis; } } diff --git a/source/src/serverchecks.h b/source/src/serverchecks.h index 9a13a0a8..3feed894 100644 --- a/source/src/serverchecks.h +++ b/source/src/serverchecks.h @@ -5,120 +5,28 @@ inline bool is_lagging(client *cl) inline bool outside_border(vec &po) { - return (po.x < 0 || po.y < 0 || po.x >= maplayoutssize || po.y >= maplayoutssize); + return sg->curmap && (po.x < sg->curmap->x1 || po.y < sg->curmap->y1 || po.x > sg->curmap->x2 || po.y > sg->curmap->y2 || po.z < sg->curmap->zmin || po.z > sg->curmap->zmax); } inline void checkclientpos(client *cl) { vec &po = cl->state.o; - if( outside_border(po) || maplayout[((int) po.x) + (((int) po.y) << maplayout_factor)] > po.z + 3) + if(outside_border(po) || (sg->curmap && sg->layout && sg->layout[((int) po.x) + (((int) po.y) << sg->curmap->sfactor)] > po.z + 3)) { - if(gamemillis > 10000 && (servmillis - cl->connectmillis) > 10000) cl->mapcollisions++; + if(sg->gamemillis > 10000 && (servmillis - cl->connectmillis) > 10000) cl->mapcollisions++; if(cl->mapcollisions && !(cl->mapcollisions % 25)) { - logline(ACLOG_INFO, "[%s] %s is colliding with the map", cl->hostname, cl->name); + xlog(ACLOG_INFO, "[%s] %s is colliding with the map", cl->hostname, cl->name); } } } -#define POW2XY(A,B) (pow2(A.x-B.x)+pow2(A.y-B.y)) - extern inline void addban(client *cl, int reason, int type = BAN_AUTO); #ifdef ACAC #include "anticheat.h" #endif -#define MINELINE 50 - -//FIXME -/* There are smarter ways to implement this function, but most probably they will be very complex */ -int getmaxarea(int inversed_x, int inversed_y, int transposed, int ml_factor, char *ml) -{ - int ls = (1 << ml_factor); - int xi = 0, oxi = 0, xf = 0, oxf = 0, fx = 0, fy = 0; - int area = 0, maxarea = 0; - bool sav_x = false, sav_y = false; - - if (transposed) fx = ml_factor; - else fy = ml_factor; - - // walk on x for each y - for ( int y = (inversed_y ? ls-1 : 0); (inversed_y ? y >= 0 : y < ls); (inversed_y ? y-- : y++) ) { - - /* Analyzing each cube of the line */ - for ( int x = (inversed_x ? ls-1 : 0); (inversed_x ? x >= 0 : x < ls); (inversed_x ? x-- : x++) ) { - if ( ml[ ( x << fx ) + ( y << fy ) ] != 127 ) { // if it is not solid - if ( sav_x ) { // if the last cube was saved - xf = x; // new end for this line - } - else { - xi = x; // new begin of the line - sav_x = true; // accumulating cubes from now - } - } else { // solid - if ( xf - xi > MINELINE ) break; // if the empty line is greater than a minimum, get out - sav_x = false; // stop the accumulation of cubes - } - } - - /* Analyzing this line with the previous one */ - if ( xf - xi > MINELINE ) { // if the line has the minimun threshold of emptiness - if ( sav_y ) { // if the last line was saved - if ( 2*oxi + MINELINE < 2*xf && - 2*xi + MINELINE < 2*oxf ) { // if the last line intersect this one - area += xf - xi; - } else { - oxi = xi; // new area vertices - oxf = xf; - } - } - else { - oxi = xi; - oxf = xf; - sav_y = true; // accumulating lines from now - } - } else { - sav_y = false; // stop the accumulation of lines - if (area > maxarea) maxarea = area; // new max area - area=0; - } - - sav_x = false; // reset x - xi = xf = 0; - } - return maxarea; -} - -int checkarea(int maplayout_factor, char *maplayout) -{ - int area = 0, maxarea = 0; - for (int i=0; i < 8; i++) { - area = getmaxarea((i & 1),(i & 2),(i & 4), maplayout_factor, maplayout); - if ( area > maxarea ) maxarea = area; - } - return maxarea; -} - -/** -This part is related to medals system. WIP - */ - -const char * medal_messages[] = { "defended the flag", "covered the flag stealer", "defended the dropped flag", "covered the flag keeper", "covered teammate" }; -enum { CTFLDEF, CTFLCOV, HTFLDEF, HTFLCOV, COVER, MEDALMESSAGENUM }; -inline void print_medal_messages(client *c, int n) -{ - if (n<0 || n>=MEDALMESSAGENUM) return; - logline(ACLOG_VERBOSE, "[%s] %s %s", c->hostname, c->name, medal_messages[n]); -} - -inline void addpt(client *c, int points, int n = -1) { - c->state.points += points; - c->md.dpt += points; - c->md.updated = true; - c->md.upmillis = gamemillis + 240; // about 2 AR shots - print_medal_messages(c,n); -} /** cnumber is the number of players in the game, at a max value of 12 */ #define CTFPICKPT cnumber // player picked the flag (ctf) @@ -150,353 +58,13 @@ inline void addpt(client *c, int points, int n = -1) { #define TKPT -20 // player tks #define FLAGTKPT -2*(10+cnumber) // player tks the flag keeper/stealer -void flagpoints(client *c, int message) -{ - float distance = 0; - int cnumber = totalclients < 13 ? totalclients : 12; - switch (message) - { - case FM_PICKUP: - c->md.flagpos.x = c->state.o.x; - c->md.flagpos.y = c->state.o.y; - c->md.flagmillis = servmillis; - if (m_ctf) addpt(c, CTFPICKPT); - break; - case FM_DROP: - if (m_ctf) addpt(c, CTFDROPPT); - break; - case FM_LOST: - if (m_htf) addpt(c, HTFLOSTPT); - else if (m_ctf) { - distance = sqrt(POW2XY(c->state.o,c->md.flagpos)); - if (distance > 200) distance = 200; // ~200 is the distance between the flags in ac_depot - addpt(c, CTFLOSTPT); - } - break; - case FM_RETURN: - addpt(c, CTFRETURNPT); - break; - case FM_SCORE: - if (m_ctf) { - distance = sqrt(POW2XY(c->state.o,c->md.flagpos)); - if (distance > 200) distance = 200; -#ifdef ACAC - if ( validflagscore(distance,c) ) -#endif - addpt(c, CTFSCOREPT); - } else addpt(c, HTFSCOREPT); - break; - case FM_KTFSCORE: - addpt(c, KTFSCOREPT); - break; - default: - break; - } -} -inline int minhits2combo(int gun) -{ - switch (gun) - { - case GUN_SUBGUN: - case GUN_AKIMBO: - return 4; - case GUN_GRENADE: - case GUN_ASSAULT: - return 3; - default: - return 2; - } -} -void checkcombo(client *target, client *actor, int damage, int gun) -{ - int diffhittime = servmillis - actor->md.lasthit; - actor->md.lasthit = servmillis; - if ((gun == GUN_SHOTGUN || gun == GUN_GRENADE) && damage < 20) - { - actor->md.lastgun = gun; - return; - } - if ( diffhittime < 750 ) { - if ( gun == actor->md.lastgun ) - { - if ( diffhittime * 2 < guns[gun].attackdelay * 3 ) - { - actor->md.combohits++; - actor->md.combotime+=diffhittime; - actor->md.combodamage+=damage; - int mh2c = minhits2combo(gun); - if ( actor->md.combohits > mh2c && actor->md.combohits % mh2c == 1 ) - { - if (actor->md.combo < 5) actor->md.combo++; - actor->md.ncombos++; - } - } - } - else - { - switch (gun) - { - case GUN_KNIFE: - case GUN_PISTOL: - if ( guns[actor->md.lastgun].isauto ) break; - case GUN_SNIPER: - if ( actor->md.lastgun > GUN_PISTOL ) break; - default: - actor->md.combohits++; - actor->md.combotime+=diffhittime; - actor->md.combodamage+=damage; - if (actor->md.combo < 5) actor->md.combo++; - actor->md.ncombos++; - break; - } - } - } - else - { - actor->md.combo = 0; - actor->md.combofrags = 0; - actor->md.combotime = 0; - actor->md.combodamage = 0; - actor->md.combohits = 0; - } - actor->md.lastgun = gun; -} -#define COVERDIST 2000 // about 45 cubes -#define REPLYDIST 8000 // about 90 cubes -float coverdist = COVERDIST; -int checkteamrequests(int sender) -{ - int dtime, besttime = -1; - int bestid = -1; - client *ant = clients[sender]; - loopv(clients) { - client *prot = clients[i]; - if ( i!=sender && prot->type != ST_EMPTY && prot->team == ant->team && - prot->state.state == CS_ALIVE && prot->md.askmillis > gamemillis && prot->md.ask >= 0 ) { - float dist = POW2XY(ant->state.o,prot->state.o); - if ( dist < REPLYDIST && (dtime=prot->md.askmillis-gamemillis) > besttime) { - bestid = i; - besttime = dtime; - } - } - } - if ( besttime >= 0 ) return bestid; - return -1; -} -/** WIP */ -void checkteamplay(int s, int sender) -{ - client *actor = clients[sender]; - - if ( actor->state.state != CS_ALIVE ) return; - switch(s){ - case S_IMONDEFENSE: // informs - actor->md.linkmillis = gamemillis + 20000; - actor->md.linkreason = s; - break; - case S_COVERME: // demands - case S_STAYTOGETHER: - case S_STAYHERE: - actor->md.ask = s; - actor->md.askmillis = gamemillis + 5000; - break; - case S_AFFIRMATIVE: // replies - case S_ALLRIGHTSIR: - case S_YES: - { - int id = checkteamrequests(sender); - if ( id >= 0 ) { - client *sgt = clients[id]; - actor->md.linked = id; - if ( actor->md.linkmillis < gamemillis ) addpt(actor,REPLYPT); - actor->md.linkmillis = gamemillis + 30000; - actor->md.linkreason = sgt->md.ask; - sendf(actor->clientnum, 1, "ri2", SV_HUDEXTRAS, HE_NUM+id); - switch( actor->md.linkreason ) { // check demands - case S_STAYHERE: - actor->md.pos = sgt->state.o; - addpt(sgt,REPLYPT); - break; - } - } - break; - } - } -} - -void computeteamwork(int team, int exclude) // testing -{ - loopv(clients) - { - client *actor = clients[i]; - if ( i == exclude || actor->type == ST_EMPTY || actor->team != team || actor->state.state != CS_ALIVE || actor->md.linkmillis < gamemillis ) continue; - vec position; - bool teamworkdone = false; - switch( actor->md.linkreason ) - { - case S_IMONDEFENSE: - position = actor->spawnp; - teamworkdone = true; - break; - case S_STAYTOGETHER: - if ( valid_client(actor->md.linked) ) position = clients[actor->md.linked]->state.o; - teamworkdone = true; - break; - case S_STAYHERE: - position = actor->md.pos; - teamworkdone = true; - break; - } - if ( teamworkdone ) - { - float dist = POW2XY(actor->state.o,position); - if (dist < COVERDIST) - { - addpt(actor,TWDONEPT); - sendf(actor->clientnum, 1, "ri2", SV_HUDEXTRAS, HE_TEAMWORK); - } - } - } -} - -float a2c = 0, c2t = 0, a2t = 0; // distances: actor to covered, covered to target and actor to target - -inline bool testcover(int msg, int factor, client *actor) -{ - if ( a2c < coverdist && c2t < coverdist && a2t < coverdist ) - { - sendf(actor->clientnum, 1, "ri2", SV_HUDEXTRAS, msg); - addpt(actor, factor); - actor->md.ncovers++; - return true; - } - return false; -} - -#define CALCCOVER(C) \ - a2c = POW2XY(actor->state.o,C);\ - c2t = POW2XY(C,target->state.o);\ - a2t = POW2XY(actor->state.o,target->state.o) - -bool validlink(client *actor, int cn) -{ - return actor->md.linked >= 0 && actor->md.linked == cn && gamemillis < actor->md.linkmillis && valid_client(actor->md.linked); -} - -/** WIP */ -void checkcover(client *target, client *actor) -{ - int team = actor->team; - int oteam = team_opposite(team); - - bool covered = false; - int coverid = -1; - - int cnumber = totalclients < 13 ? totalclients : 12; - - if ( m_flags ) { - sflaginfo &f = sflaginfos[team]; - sflaginfo &of = sflaginfos[oteam]; - - if ( m_ctf ) - { - if ( f.state == CTFF_INBASE ) - { - CALCCOVER(f); - if ( testcover(HE_FLAGDEFENDED, CTFLDEFPT, actor) ) print_medal_messages(actor,CTFLDEF); - } - if ( of.state == CTFF_STOLEN && actor->clientnum != of.actor_cn ) - { - covered = true; coverid = of.actor_cn; - CALCCOVER(clients[of.actor_cn]->state.o); - if ( testcover(HE_FLAGCOVERED, CTFLCOVPT, actor) ) print_medal_messages(actor,CTFLCOV); - } - } - else if ( m_htf ) - { - if ( actor->clientnum != f.actor_cn ) - { - if ( f.state == CTFF_DROPPED ) - { - struct { short x, y; } nf; - nf.x = f.pos[0]; nf.y = f.pos[1]; - CALCCOVER(nf); - if ( testcover(HE_FLAGDEFENDED, HTFLDEFPT, actor) ) print_medal_messages(actor,HTFLDEF); - } - if ( f.state == CTFF_STOLEN ) - { - covered = true; coverid = f.actor_cn; - CALCCOVER(clients[f.actor_cn]->state.o); - if ( testcover(HE_FLAGCOVERED, HTFLCOVPT, actor) ) print_medal_messages(actor,HTFLCOV); - } - } - } - } - - if ( !(covered && actor->md.linked==coverid) && validlink(actor,actor->md.linked) ) - { - CALCCOVER(clients[actor->md.linked]->state.o); - if ( testcover(HE_COVER, COVERPT, actor) ) print_medal_messages(actor,COVER); - } - -} - -#undef CALCCOVER - -/** WiP */ -void checkfrag(client *target, client *actor, int gun, bool gib) -{ - int targethasflag = clienthasflag(target->clientnum); - int actorhasflag = clienthasflag(actor->clientnum); - int cnumber = totalclients < 13 ? totalclients : 12; - addpt(target,DEATHPT); - if(target!=actor) { - if(!isteam(target->team, actor->team)) { - - if (m_teammode) { - if(!m_flags) addpt(actor, TMBONUSPT); - else addpt(actor, FLBONUSPT); - - checkcover (target, actor); - if ( m_htf && actorhasflag >= 0 ) addpt(actor, HTFFRAGPT); - - if ( m_ctf && targethasflag >= 0 ) { - addpt(actor, CTFFRAGPT); - } - } - else addpt(actor, BONUSPT); - - if (gib && gun != GUN_GRENADE) { - if ( gun == GUN_SNIPER ) { - addpt(actor, HEADSHOTPT); - actor->md.nhs++; - } - else if ( gun == GUN_KNIFE ) addpt(actor, KNIFEPT); - else if ( gun == GUN_SHOTGUN ) addpt(actor, SHOTGPT); - } - else addpt(actor, FRAGPT); - - if ( actor->md.combo ) { - actor->md.combofrags++; - addpt(actor,COMBOPT); - actor->md.combosend = true; - } - - } else { - - if ( targethasflag >= 0 ) addpt(actor, FLAGTKPT); - else addpt(actor, TKPT); - - } - } -} int next_afk_check = 200; @@ -511,13 +79,13 @@ void check_afk() client &c = *clients[i]; if ( c.type != ST_TCPIP || c.connectmillis + 60 * 1000 > servmillis || c.inputmillis + scl.afk_limit > servmillis || clienthasflag(c.clientnum) != -1 ) continue; - if ( ( c.state.state == CS_DEAD && !m_arena && c.state.lastdeath + 45 * 1000 < gamemillis) || + if ( ( c.state.state == CS_DEAD && !m_arena && c.state.lastdeath + 45 * 1000 < sg->gamemillis) || ( c.state.state == CS_ALIVE && c.upspawnp ) /*|| ( c.state.state == CS_SPECTATE && totalclients >= scl.maxclients ) // only kick spectator if server is full - 2011oct16:flowtron: mmh, that seems reasonable enough .. still, kicking spectators for inactivity seems harsh! disabled ATM, kick them manually if you must. */ ) { - logline(ACLOG_INFO, "[%s] %s %s", c.hostname, c.name, "is afk"); + xlog(ACLOG_INFO, "[%s] %s %s", c.hostname, c.name, "is afk"); defformatstring(msg)("%s is afk", c.name); sendservmsg(msg); disconnect_client(c.clientnum, DISC_AFK); @@ -530,13 +98,13 @@ void check_afk() In normal games, the players go over 6 tks only in the worst cases */ void check_ffire(client *target, client *actor, int damage) { - if ( mastermode != MM_OPEN ) return; + if ( sg->mastermode != MM_OPEN ) return; actor->ffire += damage; - if ( actor->ffire > 300 && actor->ffire * 600 > gamemillis) { - logline(ACLOG_INFO, "[%s] %s %s", actor->hostname, actor->name, "kicked for excessive friendly fire"); + if ( actor->ffire > 300 && actor->ffire * 600 > sg->gamemillis) { + xlog(ACLOG_INFO, "[%s] %s %s", actor->hostname, actor->name, "kicked for excessive friendly fire"); defformatstring(msg)("%s %s", actor->name, "kicked for excessive friendly fire"); sendservmsg(msg); - disconnect_client(actor->clientnum, DISC_FFIRE); + disconnect_client(actor->clientnum, DISC_FFKICK); } } @@ -555,81 +123,3 @@ inline int check_pdist(client *c, float & dist) // pick up distance return 0; } -/** -If you read README.txt you must know that AC does not have cheat protection implemented. -However this file is the sketch to a very special kind of cheat detection tools in server side. - -This is not based in program tricks, i.e., encryption, secret bytes, nor monitoring/scanning tools. - -The idea behind these cheat detections is to check (or reproduce) the client data, and verify if -this data is expected or possible. Also, there is no need to check all clients all time, and -one coding this kind of check must pay a special attention to the lag effect and how it can -affect the data observed. This is not a trivial task, and probably it is the main reason why -such tools were never implemented. - -This part is here for compatibility purposes. -If you know nothing about these detections, please, just ignore it. -*/ - -inline void checkmove(client *cl) -{ - cl->ldt = gamemillis - cl->lmillis; - cl->lmillis = gamemillis; - if ( cl->ldt < 40 ) cl->ldt = 40; - cl->t += cl->ldt; - cl->spj = (( 7 * cl->spj + cl->ldt ) >> 3); - - if ( cl->input != cl->f ) - { - cl->input = cl->f; - cl->inputmillis = servmillis; - } - - if(maplayout) checkclientpos(cl); - -#ifdef ACAC - m_engine(cl); -#endif - - if ( !cl->upspawnp ) - { - cl->spawnp = cl->state.o; - cl->upspawnp = true; - cl->spj = cl->ldt = 40; - } - - return; -} - -inline void checkshoot(int & cn, gameevent & shot, int & hits, int & tcn) -{ -#ifdef ACAC - s_engine(cn, shot, hits, tcn); -#endif - return; -} - -inline void checkweapon(int & type, int & var) -{ -#ifdef ACAC - w_engine(type,var); -#endif - return; -} - -bool validdamage(client *&target, client *&actor, int &damage, int &gun, bool &gib) -{ -#ifdef ACAC - if (!d_engine(target, actor, damage, gun, gib)) return false; -#endif - return true; -} - -inline int checkmessage(client *c, int type) -{ -#ifdef ACAC - type = p_engine(c,type); -#endif - return type; -} - diff --git a/source/src/servercontroller.h b/source/src/servercontroller.h index 911e1655..44b729bd 100644 --- a/source/src/servercontroller.h +++ b/source/src/servercontroller.h @@ -19,11 +19,13 @@ struct winservice : servercontroller HANDLE stopevent; const char *name; - winservice(const char *name) : name(name) + winservice(const char *name, int _argc, char **_argv) : name(name) { - callbacks::svc = this; + callbacks::svc = this; statushandle = 0; - }; + argc = _argc; + argv = _argv; + } ~winservice() { @@ -109,25 +111,12 @@ struct winservice : servercontroller return main(argc, argv); } - struct callbacks { static winservice *svc; static int WINAPI main() { return svc->svcmain(); }; static void WINAPI requesthandler(DWORD request) { svc->requesthandler(request); }; }; - - /*void log(const char *msg, bool error) - { - HANDLE eventsrc = RegisterEventSource(NULL, "AC Server"); - if(eventsrc) - { - int eventid = ((error ? 0x11 : 0x1) << 10) & (0x1 << 9) & (FACILITY_NULL << 6) & 0x1; // TODO: create event definitions - LPCTSTR msgs[1] = { msg }; - int r = ReportEvent(eventsrc, error ? EVENTLOG_ERROR_TYPE : EVENTLOG_INFORMATION_TYPE, 0, 4, NULL, 1, 0, msgs, NULL); - DeregisterEventSource(eventsrc); - } - }*/ }; winservice *winservice::callbacks::svc = (winservice *)NULL; diff --git a/source/src/serverevents.h b/source/src/serverevents.h index 248abbc6..19a06318 100644 --- a/source/src/serverevents.h +++ b/source/src/serverevents.h @@ -37,18 +37,18 @@ void processevent(client *c, shotevent &e) { clientstate &gs = c->state; int wait = e.millis - gs.lastshot; - if(!gs.isalive(gamemillis) || + if(!gs.isalive(sg->gamemillis) || !valid_weapon(e.gun) || waitgamemillis) return; if(e.gun!=GUN_KNIFE) gs.mag[e.gun]--; loopi(NUMGUNS) if(gs.gunwait[i]) gs.gunwait[i] = max(gs.gunwait[i] - (e.millis-gs.lastshot), 0); gs.lastshot = e.millis; gs.gunwait[e.gun] = attackdelay(e.gun); - if(e.gun==GUN_PISTOL && gs.akimbomillis>gamemillis) gs.gunwait[e.gun] /= 2; + if(e.gun==GUN_PISTOL && gs.akimbomillis>sg->gamemillis) gs.gunwait[e.gun] /= 2; sendf(-1, 1, "ri6x", SV_SHOTFX, c->clientnum, e.gun, // int(e.from[0]*DMF), int(e.from[1]*DMF), int(e.from[2]*DMF), int(e.to[0]*DMF), int(e.to[1]*DMF), int(e.to[2]*DMF), @@ -115,20 +115,20 @@ void processevent(client *c, suicideevent &e) void processevent(client *c, pickupevent &e) { clientstate &gs = c->state; - if(m_mp(gamemode) && !gs.isalive(gamemillis)) return; + if(m_mp(gamemode) && !gs.isalive(sg->gamemillis)) return; serverpickup(e.ent, c->clientnum); } void processevent(client *c, reloadevent &e) { clientstate &gs = c->state; - if(!gs.isalive(gamemillis) || + if(!gs.isalive(sg->gamemillis) || !valid_weapon(e.gun) || !reloadable_gun(e.gun) || gs.ammo[e.gun]<=0) return; - if(e.gun == GUN_AKIMBO && gs.akimbomillis < gamemillis) return; + if(e.gun == GUN_AKIMBO && gs.akimbomillis < sg->gamemillis) return; bool akimbo = e.gun==GUN_PISTOL && gs.akimbomillis>e.millis; int mag = (akimbo ? 2 : 1) * magsize(e.gun), numbullets = min(gs.ammo[e.gun], mag - gs.mag[e.gun]); if(numbullets<=0) return; @@ -150,7 +150,7 @@ void processevent(client *c, reloadevent &e) void processevent(client *c, akimboevent &e) { clientstate &gs = c->state; - if(!gs.isalive(gamemillis) || gs.akimbomillis) return; + if(!gs.isalive(sg->gamemillis) || gs.akimbomillis) return; gs.akimbomillis = e.millis+30000; } @@ -167,13 +167,13 @@ void processevents() { client *c = clients[i]; if(c->type==ST_EMPTY) continue; - if(c->state.akimbomillis && c->state.akimbomillis < gamemillis) { c->state.akimbomillis = 0; c->state.akimbo = false; } + if(c->state.akimbomillis && c->state.akimbomillis < sg->gamemillis) { c->state.akimbomillis = 0; c->state.akimbo = false; } while(c->events.length()) { gameevent &e = c->events[0]; if(e.typegamemillis) break; + if(e.shot.millis>sg->gamemillis) break; if(e.shot.millislastevent) { clearevent(c); continue; } c->lastevent = e.shot.millis; } diff --git a/source/src/serverfiles.h b/source/src/serverfiles.h index 4c5f7f4a..f1b99478 100644 --- a/source/src/serverfiles.h +++ b/source/src/serverfiles.h @@ -1,20 +1,64 @@ // serverfiles.h +// abuse globals to register server parameters +extern int addservparint(const char *name, int minval, int cur, int maxval, const char *list[], int *storage, int *shadowstorage, void (*fun)(), bool logchanges, bool fromfile, const char *desc); +extern bool addservparstr(const char *name, int minlen, int maxlen, int filt, const char *cur, char *storage, char *shadowstorage, void (*fun)(), bool logchanges, bool fromfile, const char *desc); +#ifdef _DEBUG +#define SERVPAR(name, min, cur, max, desc) int last_##name, name = addservparint(#name, min, cur, max, NULL, &name, &last_##name, NULL, false, true, desc) +#define SERVPARLIST(name, min, cur, max, list, desc) int last_##name, name = addservparint(#name, min, cur, max, list, &name, &last_##name, NULL, false, true, desc) +#define SERVPARF(name, min, cur, max, fun, desc) extern void fun(); int last_##name, name = addservparint(#name, min, cur, max, NULL,&name, &last_##name, fun, false, true, desc) +#define SERVPARLISTF(name, min, cur, max, list, fun, desc) extern void fun(); int last_##name, name = addservparint(#name, min, cur, max, list, &name, &last_##name, fun, false, true, desc) +#define SERVSTR(name, cur, min, max, filt, desc) char last_##name[max+1], name[max+1]; bool __sdummy_##name = addservparstr(#name, min, max, filt, cur, name, last_##name, NULL, false, true, desc) +#define SERVSTRF(name, cur, min, max, filt, fun, desc) char last_##name[max+1], name[max+1]; extern void fun(); bool __sdummy_##name = addservparstr(#name, min, max, filt, cur, name, last_##name, fun, false, true, desc) +#define SERVSTAT(name, cur, desc) int last_##name, name = addservparint(#name, 0, cur, 0, NULL, &name, &last_##name, NULL, false, false, desc) +#define SERVSTATLOG(name, cur, level, desc) int last_##name, name = addservparint(#name, level, cur, 0, NULL, &name, &last_##name, NULL, true, false, desc) +#else +#define SERVPAR(name, min, cur, max, desc) int last_##name, name = addservparint(#name, min, cur, max, NULL, &name, &last_##name, NULL, false, true, NULL) +#define SERVPARLIST(name, min, cur, max, list, desc) int last_##name, name = addservparint(#name, min, cur, max, list, &name, &last_##name, NULL, false, true, NULL) +#define SERVPARF(name, min, cur, max, fun, desc) extern void fun(); int last_##name, name = addservparint(#name, min, cur, max, NULL,&name, &last_##name, fun, false, true, NULL) +#define SERVPARLISTF(name, min, cur, max, list, fun, desc) extern void fun(); int last_##name, name = addservparint(#name, min, cur, max, list, &name, &last_##name, fun, false, true, NULL) +#define SERVSTR(name, cur, min, max, filt, desc) char last_##name[max+1], name[max+1]; bool __sdummy_##name = addservparstr(#name, min, max, filt, cur, name, last_##name, NULL, false, true, NULL) +#define SERVSTRF(name, cur, min, max, filt, fun, desc) char last_##name[max+1], name[max+1]; extern void fun(); bool __sdummy_##name = addservparstr(#name, min, max, filt, cur, name, last_##name, fun, false, true, NULL) +#define SERVSTAT(name, cur, desc) int last_##name, name = addservparint(#name, 0, cur, 0, NULL, &name, &last_##name, NULL, false, false, desc) +#define SERVSTATLOG(name, cur, level, desc) int last_##name, name = addservparint(#name, level, cur, 0, NULL, &name, &last_##name, NULL, true, false, desc) +#endif + +#define CONFIG_MAXPAR 11 +const char *maprotkeywords[CONFIG_MAXPAR] = { "weight", "repeat", "time", "mintime", "maxtime", "minplayers", "maxplayers", "maxteamsize", "teamthreshold", "manual", "restrict" }; + +struct configsetvalues +{ + union + { + struct { char weight, repeat, time, mintime, maxtime, minplayers, maxplayers, maxteamsize, teamthreshold, manual, restrict; }; + char par[CONFIG_MAXPAR]; + }; + + void empty() { weight = repeat = 0; time = mintime = maxtime = minplayers = maxplayers = maxteamsize = teamthreshold = manual = restrict = -1; } + void add(configsetvalues u); + bool parse(char *keyval); +}; + // map management #define SERVERMAXMAPFACTOR 10 // 10 is huge... if you're running low on RAM, consider 9 as maximum (don't go higher than 10, or players will name their ugly dog after you) #define SERVERMAP_PATH_BUILTIN "packages" PATHDIVS "maps" PATHDIVS "official" PATHDIVS +#define SERVERMAP_PATH_LOCAL "packages" PATHDIVS "maps" PATHDIVS #define SERVERMAP_PATH "packages" PATHDIVS "maps" PATHDIVS "servermaps" PATHDIVS #define SERVERMAP_PATH_INCOMING "packages" PATHDIVS "maps" PATHDIVS "servermaps" PATHDIVS "incoming" PATHDIVS const char *servermappath_off = SERVERMAP_PATH_BUILTIN; +const char *servermappath_local = SERVERMAP_PATH_LOCAL; const char *servermappath_serv = SERVERMAP_PATH; const char *servermappath_incom = SERVERMAP_PATH_INCOMING; #define GZBUFSIZE ((MAXCFGFILESIZE * 11) / 10) #define FLOORPLANBUFSIZE (sizeof(struct servsqr) << ((SERVERMAXMAPFACTOR) * 2)) // that's 4MB for size 10 maps (or 1 MB for size 9 maps) +#define GAMEHISTLEN 32 // account the last half hour that the map was played +#define PENALTYUNIT 100 + stream *readmaplog = NULL; // the readmaps thread always logs directly to file struct servermap // in-memory version of a map file on a server @@ -38,58 +82,101 @@ struct servermap // in-memory version of a map file on a server uchar *enttypes; // table of entity types short *entpos_x, *entpos_y; + configsetvalues *hx_modeinfo; // maprot parameters from map author + uchar *hx_mapartist; // pubkey of map artist + + int modes_allowed; // modes_possible reduced to MP modes (if dedicated server) + configsetvalues modes[GMODE_NUM]; // maprot parameters for this map + int modes_auto; // modes to be suggested automatically + int modes_cr[CR_NUM]; // modes available for manual voting, depending on client role + int modes_pn[MAXPLAYERS]; // modes suited for a certain number of players + char lastmodes[GAMEHISTLEN]; // last played game modes on this map (one entry per minute) + int lastplayed[GAMEHISTLEN]; // time the map was played last (one entry per minute) + int penalty[GMODE_NUM]; // prevent overplaying a mode on this map + int mappenalty; // prevent overplaying of map + int bestmode, weight; // mode with least penalty, weight of that mode on this map + + const char *err; bool isok; // definitive flag! #ifdef _DEBUG char maptitle[129]; #endif servermap(const char *mname, const char *mpath) { memset(&fname, 0, sizeof(struct servermap)); fname = newstring(mname); fpath = mpath; } - ~servermap() { delstring(fname); DELETEA(cgzraw); DELETEA(cfgrawgz); DELETEA(enttypes); DELETEA(entpos_x); DELETEA(entpos_y); DELETEA(layoutgz); } + ~servermap() { delstring(fname); DELETEA(cgzraw); DELETEA(cfgrawgz); DELETEA(enttypes); DELETEA(entpos_x); DELETEA(entpos_y); DELETEA(layoutgz); DELETEA(hx_modeinfo); DELETEA(hx_mapartist); } bool isro() { return fpath == servermappath_off || fpath == servermappath_serv; } bool isofficial() { return fpath == servermappath_off; } + bool isdistributable() { return fpath == servermappath_serv || fpath == servermappath_incom; } + bool isautoplay() { return isro(); } int getmemusage() { return sizeof(struct servermap) + cgzlen + cfggzlen + layoutgzlen + numents * (sizeof(uchar) + sizeof(short) * 3); } + char *getlayout() // uncompress prefabricated floorplan + { + if(isok && layoutgz && layoutgzlen > 0 && layoutgzlen < layoutlen) + { + char *lo = new char[layoutlen]; + uLongf rawsize = layoutlen; + if(lo && uncompress((Bytef*)lo, &rawsize, layoutgz, layoutgzlen) == Z_OK && rawsize - layoutlen == 0) return lo; + delete[] lo; + } + return NULL; + } + + const char *getpathdesc() + { + if(fpath == servermappath_off) return "official"; + if(fpath == servermappath_local) return "local"; + if(fpath == servermappath_serv) return "custom"; + if(fpath == servermappath_incom) return "temporary"; + ASSERT(0); return "unknown"; + } + void load(void) // load map into memory and extract everything important about it (assumes struct to be zeroed: can only be called once) { static uchar *staticbuffer = NULL; if(!staticbuffer) staticbuffer = new uchar[FLOORPLANBUFSIZE]; // this buffer gets reused for every map load several times (also: because of this, load() is not thread safe) - const char *err = NULL; stream *f = NULL; int restofhead; + string filename, tmp; + + if(!validmapname(fname)) err = "illegal filename"; + if(err) goto loadfailed; // load map files, prepare sendmap buffer - defformatstring(filename)("%s%s.cfg", fpath, fname); - path(filename); - uchar *cfgraw = (uchar *)loadfile(filename, &cfglen); - if(cfgraw) { - tigerhash(cfghash, cfgraw, cfglen); - loopk(cfglen) if(cfgraw[k] > 0x7f || (cfgraw[k] < 0x20 && !isspace(cfgraw[k]))) err = "illegal chars in cfg file"; - } - formatstring(filename)("%s%s.cgz", fpath, fname); - path(filename); - cgzraw = (uchar *)loadfile(filename, &cgzlen); - if(cgzraw) tigerhash(cgzhash, cgzraw, cgzlen); - if(!cgzraw) err = "loading cgz failed"; - else if(cfglen > MAXCFGFILESIZE) err = "cfg file too big"; - else if(cgzlen >= MAXMAPSENDSIZE) err = "cgz file too big"; - else if(cfgraw) - { - uLongf gzbufsize = GZBUFSIZE; - ASSERT(GZBUFSIZE < FLOORPLANBUFSIZE); - if(compress2(staticbuffer, &gzbufsize, cfgraw, cfglen, 9) != Z_OK) gzbufsize = 0; - cfggzlen = (int) gzbufsize; - if(cgzlen + cfggzlen < MAXMAPSENDSIZE) - { // map is small enough to be sent - cfgrawgz = new uchar[cfggzlen]; - memcpy(cfgrawgz, staticbuffer, cfggzlen); + formatstring(filename)("%s%s.cfg", fpath, fname); + path(filename); + uchar *cfgraw = (uchar *)loadfile(filename, &cfglen); + if(cfgraw) + { + tigerhash(cfghash, cfgraw, cfglen); + loopk(cfglen) if(cfgraw[k] > 0x7f || (cfgraw[k] < 0x20 && !isspace(cfgraw[k]))) err = "illegal chars in cfg file"; } - else err = "cgz + cfg.gz too big to send"; + formatstring(filename)("%s%s.cgz", fpath, fname); + path(filename); + cgzraw = (uchar *)loadfile(filename, &cgzlen); + if(cgzraw) tigerhash(cgzhash, cgzraw, cgzlen); + if(!cgzraw) err = "loading cgz failed"; + else if(cfglen > MAXCFGFILESIZE) err = "cfg file too big"; + else if(cgzlen >= MAXMAPSENDSIZE) err = "cgz file too big"; + else if(cfgraw) + { + uLongf gzbufsize = GZBUFSIZE; + ASSERT(GZBUFSIZE < FLOORPLANBUFSIZE); + if(compress2(staticbuffer, &gzbufsize, cfgraw, cfglen, 9) != Z_OK) gzbufsize = 0; + cfggzlen = (int) gzbufsize; + if(cgzlen + cfggzlen < MAXMAPSENDSIZE) + { // map is small enough to be sent + cfgrawgz = new uchar[cfggzlen]; + memcpy(cfgrawgz, staticbuffer, cfggzlen); + } + else err = "cgz + cfg.gz too big to send"; + } + DELETEA(cfgraw); } - DELETEA(cfgraw); if(err) goto loadfailed; // extract entity data and header info; compile map statistics; create floorplan @@ -141,9 +228,27 @@ struct servermap // in-memory version of a map file on a server case HX_CONFIG: break; - case HX_MAPINFO: - case HX_MODEINFO: // for new maprot... + case HX_MODEINFO: + { + hx_modeinfo = new configsetvalues[GMODE_NUM]; + char done[GMODE_NUM] = { 0 }; + loopi(GMODE_NUM) hx_modeinfo[i].empty(); + string line; + for(int mode = getuint(q); !q.overread() && mode > 0; mode = getuint(q)) + { + getstring(line, q, MAXSTRLEN); + loopi(GMODE_NUM) if((mode & (1 << i)) && !done[i]++) filterconfigset(line, hx_modeinfo + i); + stream *mr = modeinfologfilename ? openfile(modeinfologfilename, "a") : NULL; + if(mr) { mr->printf("%s %s %s\n", fname, gmode_enum(mode, tmp), line); delete mr; } // dump modeinfo lines to file, may be used as maprot + } + break; + } case HX_ARTIST: + if(len == 32) q.get((hx_mapartist = new uchar[32]), 32); + else err = "invalid HX_ARTIST record"; + break; + + case HX_MAPINFO: default: break; } @@ -204,6 +309,8 @@ struct servermap // in-memory version of a map file on a server entpos_x[i] = e.x; entpos_y[i] = e.y; } + // respect map author wishes about disallowed modes + if(hx_modeinfo) loopi(GMODE_NUM) if(hx_modeinfo[i].restrict == 81) entstats.modes_possible &= ~(1 << i); // disallowed by elvis^2 } if(err) goto loadfailed; @@ -292,6 +399,7 @@ struct servermap // in-memory version of a map file on a server if(err) goto loadfailed; // run mipmapper (to get corners right) + // TODO :) // calculate area statistics from type & vdelta values (destroys vdelta!) @@ -370,8 +478,19 @@ struct servermap // in-memory version of a map file on a server isok = true; } } + + void oneminuteplayed(int tm, char mode) + { + memmove(lastmodes + 1, lastmodes, GAMEHISTLEN - 1); + memmove(lastplayed + 1, lastplayed, GAMEHISTLEN - 1); + lastmodes[0] = mode; + lastplayed[0] = tm; + } }; +int servermapsortname(servermap **a, servermap **b) { return strcmp((*a)->fname, (*b)->fname); } // sort by name (ascending) +int servermapsortweight(servermap **a, servermap **b) { return (*b)->weight - (*a)->weight; } // sort by weight (descending) + // data structures to sync data flow between main thread and readmapsthread volatile servermap *servermapdropbox = NULL; // changed servermap entry back to the main thread volatile bool startnewservermapsepoch = false; // signal readmapsthread to start an new full search @@ -448,7 +567,7 @@ void tagmapfile(const char *fpath, const char *fname, int epoch) // tag list en if(mapfileindex >= 0) mapfilenames[mapfileindex].epoch = epoch; } -int readmapsthread(void *logfileprefix) +int readmapsthread(void *logfilename) { static int readmaps_epoch = 0; @@ -456,12 +575,7 @@ int readmapsthread(void *logfileprefix) { while(!startnewservermapsepoch) readmapsthread_sem->wait(); // wait without using the cpu - if(logfileprefix) - { - defformatstring(logfilename)("%sreadmaps_log.txt", (const char *)logfileprefix); - path(logfilename); - readmaplog = openfile(logfilename, "a"); - } + if(logfilename) readmaplog = openfile((const char *)logfilename, "a"); vector maps_off, maps_serv, maps_incom; @@ -504,207 +618,69 @@ int readmapsthread(void *logfileprefix) return 0; } - - - - - - - -struct servermapbuffer // sending of maps between clients +#ifndef STANDALONE +const char *checklocalmap(const char *mapname) // check, if a map file exists in packages/maps/official or packages/maps - return proper servermap-path, if found { - string mapname; - int cgzsize, cfgsize, cfgsizegz, revision, datasize; - uchar *data, *gzbuf; + const char **fn = setnames(behindpath(mapname)), *locs[2] = { servermappath_off, servermappath_local }; + int l = 0; + if(!isdedicated && (getfilesize(fn[l]) > 10 || getfilesize(fn[++l]) > 10)) return locs[l]; + return NULL; +} +#endif - servermapbuffer() : data(NULL) { gzbuf = new uchar[GZBUFSIZE]; } - ~servermapbuffer() { delete[] gzbuf; } - - void clear() { DELETEA(data); revision = 0; } - - int available() +bool serverwritemap(const char *mapname, int mapsize, int cfgsize, int cfgsizegz, uchar *data) +{ + FILE *fp; + bool written = false; + if(!mapname[0] || mapsize <= 0 || cfgsizegz < 0 || mapsize + cfgsizegz > MAXMAPSENDSIZE || cfgsize > MAXCFGFILESIZE) return false; // malformed: probably modded client + defformatstring(name)(SERVERMAP_PATH_INCOMING "%s.cgz", mapname); + path(name); + fp = fopen(name, "wb"); + if(fp) { - if(data && !strcmp(mapname, behindpath(smapname)) && cgzsize == smapstats.cgzsize) - { - if( !revision || (revision == smapstats.hdr.maprevision)) return cgzsize; - } - return 0; - } - - void setrevision() - { - if(available() && !revision) revision = smapstats.hdr.maprevision; - } - - void load(void) // load currently played map into the buffer (if distributable), clear buffer otherwise - { - string cgzname, cfgname; - const char *name = behindpath(smapname); // no paths allowed here - - clear(); - formatstring(cgzname)(SERVERMAP_PATH "%s.cgz", name); - path(cgzname); - if(fileexists(cgzname, "r")) - { - formatstring(cfgname)(SERVERMAP_PATH "%s.cfg", name); - } - else - { - formatstring(cgzname)(SERVERMAP_PATH_INCOMING "%s.cgz", name); - path(cgzname); - formatstring(cfgname)(SERVERMAP_PATH_INCOMING "%s.cfg", name); - } - path(cfgname); - uchar *cgzdata = (uchar *)loadfile(cgzname, &cgzsize); - uchar *cfgdata = (uchar *)loadfile(cfgname, &cfgsize); - if(cgzdata && (!cfgdata || cfgsize < MAXCFGFILESIZE)) - { - uLongf gzbufsize = GZBUFSIZE; - if(!cfgdata || compress2(gzbuf, &gzbufsize, cfgdata, cfgsize, 9) != Z_OK) - { - cfgsize = 0; - gzbufsize = 0; - } - cfgsizegz = (int) gzbufsize; - if(cgzsize + cfgsizegz < MAXMAPSENDSIZE) - { // map is ok, fill buffer - copystring(mapname, name); - datasize = cgzsize + cfgsizegz; - data = new uchar[datasize]; - memcpy(data, cgzdata, cgzsize); - memcpy(data + cgzsize, gzbuf, cfgsizegz); - logline(ACLOG_INFO,"loaded map %s, %d + %d(%d) bytes.", cgzname, cgzsize, cfgsize, cfgsizegz); - } - } - DELETEA(cgzdata); - DELETEA(cfgdata); - } - - bool sendmap(const char *nmapname, int nmapsize, int ncfgsize, int ncfgsizegz, uchar *ndata) - { - FILE *fp; - bool written = false; - - if(!nmapname[0] || nmapsize <= 0 || ncfgsizegz < 0 || nmapsize + ncfgsizegz > MAXMAPSENDSIZE || ncfgsize > MAXCFGFILESIZE) return false; // malformed: probably modded client - int cfgsize = ncfgsize; - if(smode == GMODE_COOPEDIT && !strcmp(nmapname, behindpath(smapname))) - { // update mapbuffer only in coopedit mode (and on same map) - copystring(mapname, nmapname); - datasize = nmapsize + ncfgsizegz; - revision = 0; - DELETEA(data); - data = new uchar[datasize]; - memcpy(data, ndata, datasize); - } - - defformatstring(name)(SERVERMAP_PATH_INCOMING "%s.cgz", nmapname); + fwrite(data, 1, mapsize, fp); + fclose(fp); + formatstring(name)(SERVERMAP_PATH_INCOMING "%s.cfg", mapname); path(name); fp = fopen(name, "wb"); if(fp) { - fwrite(ndata, 1, nmapsize, fp); + uLongf rawsize = cfgsize; + uchar *gzbuf = new uchar[cfgsize]; + if(uncompress(gzbuf, &rawsize, data + mapsize, cfgsizegz) == Z_OK && rawsize - cfgsize == 0) + fwrite(gzbuf, 1, cfgsize, fp); fclose(fp); - formatstring(name)(SERVERMAP_PATH_INCOMING "%s.cfg", nmapname); - path(name); - fp = fopen(name, "wb"); - if(fp) - { - uLongf rawsize = ncfgsize; - if(uncompress(gzbuf, &rawsize, ndata + nmapsize, ncfgsizegz) == Z_OK && rawsize - ncfgsize == 0) - fwrite(gzbuf, 1, cfgsize, fp); - fclose(fp); - written = true; - } + delete[] gzbuf; + written = true; } - return written; } - - void sendmap(client *cl, int chan) - { - if(!available()) return; - packetbuf p(MAXTRANS + datasize, ENET_PACKET_FLAG_RELIABLE); - putint(p, SV_RECVMAP); - sendstring(mapname, p); - putint(p, cgzsize); - putint(p, cfgsize); - putint(p, cfgsizegz); - putint(p, revision); - p.put(data, datasize); - sendpacket(cl->clientnum, chan, p.finalize()); - } -}; - - -// provide maps by the server - -enum { MAP_NOTFOUND = 0, MAP_TEMP, MAP_CUSTOM, MAP_LOCAL, MAP_OFFICIAL, MAP_VOID }; -static const char * const maplocstr[] = { "not found", "temporary", "custom", "local", "official", "void" }; -#define readonlymap(x) ((x) >= MAP_CUSTOM) -#define distributablemap(x) ((x) == MAP_TEMP || (x) == MAP_CUSTOM) - -int findmappath(const char *mapname, char *filename) -{ - if(!mapname[0]) return MAP_NOTFOUND; - string tempname; - if(!filename) filename = tempname; - const char *name = behindpath(mapname); - formatstring(filename)(SERVERMAP_PATH_BUILTIN "%s.cgz", name); - path(filename); - int loc = MAP_NOTFOUND; - if(getfilesize(filename) > 10) loc = MAP_OFFICIAL; - else - { -#ifndef STANDALONE - copystring(filename, setnames(name)); - if(!isdedicated && getfilesize(filename) > 10) loc = MAP_LOCAL; - else - { -#endif - formatstring(filename)(SERVERMAP_PATH "%s.cgz", name); - path(filename); - if(isdedicated && getfilesize(filename) > 10) loc = MAP_CUSTOM; - else - { - formatstring(filename)(SERVERMAP_PATH_INCOMING "%s.cgz", name); - path(filename); - if(isdedicated && getfilesize(filename) > 10) loc = MAP_TEMP; - } -#ifndef STANDALONE - } -#endif - } - return loc; + return written; } -mapstats *getservermapstats(const char *mapname, bool getlayout, int *maploc) -{ - string filename; - int ml; - if(!maploc) maploc = &ml; - *maploc = findmappath(mapname, filename); - if(getlayout) DELETEA(maplayout); - return *maploc == MAP_NOTFOUND ? NULL : loadmapstats(filename, getlayout); -} - - // server config files -void serverconfigfile::init(const char *name) +vector serverconfigs; // list of server config files, automatically polled in the background +volatile bool recheckallserverconfigs = false; // signal readserverconfigsthread to poll all files +sl_semaphore *readserverconfigsthread_sem = NULL; // sync readmapsthread with main thread + +void serverconfigfile::init(const char *name, bool tracking) { copystring(filename, name); path(filename); - read(); + clear(); + if(tracking) serverconfigs.add(this); } bool serverconfigfile::load() { DELETEA(buf); buf = loadfile(filename, &filelen); - if(!buf) + if((readfailed = !buf)) { - logline(ACLOG_INFO,"could not read config file '%s'", filename); + xlog(ACLOG_INFO,"could not read config file '%s'", filename); return false; } + entropy_add_block((const uchar *)buf, filelen); char *p; if('\r' != '\n') // this is not a joke! { @@ -720,136 +696,317 @@ bool serverconfigfile::load() return true; } +bool serverconfigfile::isbusy() +{ + return busy.getvalue() < 1; +} + +void serverconfigfile::trypreload() // in readserverconfigsthread: load changed files into RAM +{ + if(!buf && !isbusy()) + { + int newfilelen; + bool contentchanged = false; + char *newbuf = loadfile(filename, &newfilelen); + readfailed = !newbuf; + if(newbuf) + { + uint32_t newhash; + uchar *p = (uchar*)newbuf; + fnv1a_init(newhash); + loopirev(newfilelen) fnv1a_add(newhash, *p++); + if(newhash != filehash) + { + contentchanged = true; + filehash = newhash; + } + delete newbuf; + } + if(contentchanged) load(); + } +} + +void serverconfigfile::process() // in main thread: refill data structures with preloaded file content +{ + updated = false; + if((readfailed || buf) && !isbusy() && !busy.trywait()) + { + clear(); + if(buf && strlen(buf)) read(); + DELETEA(buf); + busy.post(); + updated = true; + } +} + +string vitafilename, vitafilename_backup, vitafilename_update, vitafilename_update_backup_base, vitafilename_update_backup; +char *vitaupdatebuf = NULL; +int vitaupdatebuflen = 0; +vector *vitastosave = NULL; + +int readserverconfigsthread(void *data) +{ + while(1) + { + while(!recheckallserverconfigs) readserverconfigsthread_sem->wait(); // wait without using the cpu + + loopv(serverconfigs) serverconfigs[i]->trypreload(); + + // special treatment for vita updates: read once and move out of the way + if(!vitaupdatebuf && getfilesize(vitafilename_update) > 0) + { + formatstring(vitafilename_update_backup)("%s%d.cfg", vitafilename_update_backup_base, (int)(time(NULL) / 60)); + backup(vitafilename_update, vitafilename_update_backup); + serverconfigfile vsf; + vsf.init(vitafilename_update_backup, false); + vsf.load(); + vitaupdatebuf = vsf.buf; + vitaupdatebuflen = vsf.filelen; + vsf.buf = NULL; + } + if(vitastosave) + { // mainloop prepared a selection of vitas to save + backup(vitafilename, vitafilename_backup); + stream *f = openfile(vitafilename, "w"); + extern void writevitas(stream *f, vector &vs); + if(f) writevitas(f, *vitastosave); + DELETEP(f); DELETEP(vitastosave); + } + + entropy_save(); + recheckallserverconfigs = false; + } + return 0; +} + // maprot.cfg -#define CONFIG_MAXPAR 6 +#define MODEHISTLEN 64 // remember the game modes of the last hour (roughly) -struct configset +void configsetvalues::add(configsetvalues u) { - string mapname; - union - { - struct { int mode, time, vote, minplayer, maxplayer, skiplines; }; - int par[CONFIG_MAXPAR]; - }; -}; - -int FlagFlag = MINFF * 1000; -int Mvolume, Marea, SHhits, Mopen = 0; -float Mheight = 0; - -bool mapisok(mapstats *ms) -{ - if ( Mheight > MAXMHEIGHT ) { logline(ACLOG_INFO, "MAP CHECK FAIL: The overall ceil height is too high (%.1f cubes)", Mheight); return false; } - if ( Mopen > MAXMAREA ) { logline(ACLOG_INFO, "MAP CHECK FAIL: There is a big open area in this (hint: use more solid walls)"); return false; } - if ( SHhits > MAXHHITS ) { logline(ACLOG_INFO, "MAP CHECK FAIL: Too high height in some parts of the map (%d hits)", SHhits); return false; } - - if ( ms->hasflags ) // Check if flags are ok - { - struct { short x, y; } fl[2]; - loopi(2) - { - if(ms->flags[i] == 1) - { - short *fe = ms->entposs + ms->flagents[i] * 3; - fl[i].x = *fe; fe++; fl[i].y = *fe; - } - else fl[i].x = fl[i].y = 0; // the map has no valid flags - } - FlagFlag = pow2(fl[0].x - fl[1].x) + pow2(fl[0].y - fl[1].y); - } - else FlagFlag = MINFF * 1000; // the map has no flags - - if ( FlagFlag < MINFF ) { logline(ACLOG_INFO, "MAP CHECK FAIL: The flags are too close to each other"); return false; } - - for (int i = 0; i < ms->hdr.numents; i++) - { - int v = ms->enttypes[i]; - if (v < I_CLIPS || v > I_AKIMBO) continue; - short *p = &ms->entposs[i*3]; - float density = 0, hdensity = 0; - for(int j = 0; j < ms->hdr.numents; j++) - { - int w = ms->enttypes[j]; - if (w < I_CLIPS || w > I_AKIMBO || i == j) continue; - short *q = &ms->entposs[j*3]; - float r2 = 0; - loopk(3){ r2 += (p[k]-q[k])*(p[k]-q[k]); } - if ( r2 == 0.0f ) { logline(ACLOG_INFO, "MAP CHECK FAIL: Items too close %s %s (%hd,%hd)", entnames[v], entnames[w],p[0],p[1]); return false; } - r2 = 1/r2; - if (r2 < 0.0025f) continue; - if (w != v) - { - hdensity += r2; - continue; - } - density += r2; - } -/* if (hdensity > 0.0f) { logline(ACLOG_INFO, "ITEM CHECK H %s %f", entnames[v], hdensity); } - if (density > 0.0f) { logline(ACLOG_INFO, "ITEM CHECK D %s %f", entnames[v], density); }*/ - if ( hdensity > 0.5f ) { logline(ACLOG_INFO, "MAP CHECK FAIL: Items too close %s %.2f (%hd,%hd)", entnames[v],hdensity,p[0],p[1]); return false; } - switch(v) - { -#define LOGTHISSWITCH(X) if( density > X ) { logline(ACLOG_INFO, "MAP CHECK FAIL: Items too close %s %.2f (%hd,%hd)", entnames[v],density,p[0],p[1]); return false; } - case I_CLIPS: - case I_HEALTH: LOGTHISSWITCH(0.24f); break; - case I_AMMO: LOGTHISSWITCH(0.04f); break; - case I_HELMET: LOGTHISSWITCH(0.02f); break; - case I_ARMOUR: - case I_GRENADE: - case I_AKIMBO: LOGTHISSWITCH(0.005f); break; - default: break; -#undef LOGTHISSWITCH - } - } - return true; + weight = clamp(weight + u.weight, -100, 100); + repeat = clamp(repeat + u.repeat, -100, 100); + for(int i = 2; i < CONFIG_MAXPAR; i++) if(u.par[i] >= 0) par[i] = u.par[i]; } +bool configsetvalues::parse(char *keyval) // parse one key:value pair +{ + char *kb, *k = strtok_r(keyval, ":", &kb); + if(k) + { + loopi(CONFIG_MAXPAR) if(!strcmp(k, maprotkeywords[i])) + { + if((k = strtok_r(NULL, ":", &kb))) + { + par[i] = clamp(atoi(k), -100, 100); + return true; + } + } + } + return false; +} + +void filterconfigset(char *list, void *cs) +{ + configsetvalues e, u, *c = cs ? (configsetvalues *) cs : &u; + e.empty(), c->empty(); + char *b, *kv = strtok_r(list, " ", &b); + while(kv) + { + c->parse(kv); + kv = strtok_r(NULL, " ", &b); + } + list[0] = '\0'; + loopi(CONFIG_MAXPAR) if(c->par[i] != e.par[i]) concatformatstring(list, "%s%s:%d", list[0] ? " " : "", maprotkeywords[i], c->par[i]); +} + +SERVSTRF(use_hx_modeinfo, "weight|time|mintime|maxtime|minplayers|maxplayers|maxteamsize|teamthreshold", 0, 159, FTXT__MAPROT, filter_use_hx_modeinfo, "gList of maprot keywords to copy from map headers"); + +int use_hx_modeinfo_mask = 0; + +void filter_use_hx_modeinfo() +{ + use_hx_modeinfo_mask = 0; + char *tmp = newstring(use_hx_modeinfo), *b, *kv = strtok_r(tmp, "|", &b); + while(kv) + { + loopi(CONFIG_MAXPAR) if(!strcmp(kv, maprotkeywords[i])) use_hx_modeinfo_mask |= 1 << i; + kv = strtok_r(NULL, "|", &b); + } + delstring(tmp); +} + +struct configset : configsetvalues +{ + char mapname[MAXMAPNAMELEN + 1]; + int mmask; + char asterisk; +}; + struct servermaprot : serverconfigfile { - vector configsets; - int curcfgset; + vector configsets, preloaded; + configsetvalues base[GMODE_NUM], prebase[GMODE_NUM]; - servermaprot() : curcfgset(-1) {} + int lastplayedmodepointer; + char lastplayedmodes[MODEHISTLEN]; + int modepenalty[GMODE_NUM]; + servermap *current; + + servermaprot() + { + lastplayedmodepointer = 0; + loopi(MODEHISTLEN) lastplayedmodes[i] = -1; + current = NULL; + } + + void clear() + { + configsets.shrink(0); + loopi(GMODE_NUM) base[i].empty(); + } + + bool load() + { + preloaded.shrink(0); + loopi(GMODE_NUM) prebase[i].empty(); + if(serverconfigfile::load()) + { + configset c, cempty; + memset(&cempty, 0, sizeof(cempty)); + cempty.empty(); + int line = 0; + char *b, *l, *p = buf; + while(p < buf + filelen) + { + l = p; p += strlen(p) + 1; line++; + l = strtok_r(l, " ", &b); + if(l) + { + c = cempty; + char *e = strchr(l, '*'); + c.asterisk = e ? clamp(int(e - l), 0, MAXMAPNAMELEN) : -1; + filtertext(c.mapname, behindpath(l), FTXT__MAPNAME, MAXMAPNAMELEN); + bool haskeywords = false; + l = strtok_r(NULL, " ", &b); + if(l) c.mmask = gmode_parse(l); + while((l = strtok_r(NULL, " ", &b))) + { + if(c.parse(l)) haskeywords = true; + } + if(haskeywords) + { + if(c.asterisk == 0) + { + loopj(GMODE_NUM) if((1 << j) & c.mmask) prebase[j].add(c); + } + else preloaded.add(c); + } + } + } + // prepare compiled maprot message for clients + return true; + } + return false; + } void read() { - if(getfilesize(filename) == filelen) return; - configsets.shrink(0); - if(!load()) return; - - const char *sep = ": "; - configset c; - int i, line = 0; - char *b, *l, *p = buf; - logline(ACLOG_VERBOSE,"reading map rotation '%s'", filename); - while(p < buf + filelen) - { - l = p; p += strlen(p) + 1; line++; - l = strtok_r(l, sep, &b); - if(l) - { - copystring(c.mapname, behindpath(l)); - for(i = 3; i < CONFIG_MAXPAR; i++) c.par[i] = 0; // default values - for(i = 0; i < CONFIG_MAXPAR; i++) - { - if((l = strtok_r(NULL, sep, &b)) != NULL) c.par[i] = atoi(l); - else break; - } - if(i > 2) - { - configsets.add(c); - logline(ACLOG_VERBOSE," %s, %s, %d minutes, vote:%d, minplayer:%d, maxplayer:%d, skiplines:%d", c.mapname, modestr(c.mode, false), c.time, c.vote, c.minplayer, c.maxplayer, c.skiplines); - } - else logline(ACLOG_INFO," error in line %d, file %s", line, filename); - } - } - DELETEA(buf); - logline(ACLOG_INFO,"read %d map rotation entries from '%s'", configsets.length(), filename); - return; + loopi(GMODE_NUM) base[i] = prebase[i]; + loopv(preloaded) configsets.add(preloaded[i]); + mlog(ACLOG_INFO,"read %d map rotation entries from '%s'", configsets.length(), filename); } - int next(bool notify = true, bool nochange = false) // load next maprotation set + void initmap(servermap *m, stream *f) // set maprot parameters of a servermap + { + configsetvalues e; + memcpy(m->modes, base, sizeof(m->modes)); + if(m->hx_modeinfo && use_hx_modeinfo_mask) loopj(GMODE_NUM) + { + e.empty(); + loopk(CONFIG_MAXPAR) if(use_hx_modeinfo_mask & (1 << k)) e.par[k] = m->hx_modeinfo[j].par[k]; + m->modes[j].add(e); + } + + loopv(configsets) + { + configset &c = configsets[i]; + if(c.asterisk < 0 ? !strcmp(c.mapname, m->fname) : !strncmp(c.mapname, m->fname, c.asterisk)) + { + loopj(GMODE_NUM) if((1 << j) & c.mmask) m->modes[j].add(c); + } + } + m->modes_allowed = m->entstats.modes_possible & (isdedicated ? GMMASK__MPNOCOOP : GMMASK__ALL); + int rm[CR_NUM] = { 0 }, man = 0; + loopj(GMODE_NUM) + { + if(m->modes[j].manual > 0) man |= 1 << j; + for(int i = max(0, (int)m->modes[j].restrict); i < CR_NUM; i++) rm[i] |= 1 << j; + } + m->modes_auto = m->isautoplay() ? m->modes_allowed & ~man : 0; + loopi(CR_NUM) m->modes_cr[i] = m->modes_allowed & rm[i]; + loopi(MAXPLAYERS) m->modes_pn[i] = 0; + loopj(GMODE_NUM) + { + if((1 << j) & m->modes_allowed) + { + int lb = m->modes[j].minplayers < 1 ? 0 : m->modes[j].minplayers - 1, ub = m->modes[j].maxplayers < 1 || m->modes[j].maxplayers > MAXPLAYERS ? MAXPLAYERS : m->modes[j].maxplayers; + while(lb < ub) m->modes_pn[lb++] |= 1 << j; + } + } + if(f) + { + string s; + f->printf("maprot parameters for: %s%s\n", m->fpath, m->fname); + e.empty(); + loopj(GMODE_NUM) if((1 << j) & GMMASK__MPNOCOOP) + { + f->printf(" mode %s:", gmode_enum(1 << j, s)); + loopk(CONFIG_MAXPAR) if(e.par[k] != m->modes[j].par[k]) f->printf(" %s:%d", maprotkeywords[k], m->modes[j].par[k]); + f->printf("\n"); + } + f->printf(" modes_allowed %s\n", gmode_enum(m->modes_allowed, s)); + f->printf(" modes_auto %s\n", gmode_enum(m->modes_auto, s)); + f->printf(" modes man %s\n", gmode_enum(man, s)); + loopi(CR_NUM) f->printf(" modes_cr[%d] %s\n", i, gmode_enum(m->modes_cr[i], s)); + loopi(MAXPLAYERS) f->printf(" modes_pn[%02d] %s\n", i + 1, gmode_enum(m->modes_pn[i], s)); + f->printf("\n"); + } + } + + void oneminutepassed(int mode) + { + lastplayedmodes[lastplayedmodepointer++] = mode; + lastplayedmodepointer %= MODEHISTLEN; + } + + void calcmodepenalties(int weight) // sum up, how many minutes each game mode was played during the last hour (including server-empty times) + { + weight = (weight * PENALTYUNIT) / 100; + loopi(GMODE_NUM) modepenalty[i] = 0; + loopi(MODEHISTLEN) + { + int n = lastplayedmodes[i]; + if(n >= 0 && (GMMASK__MPNOCOOP & (1 << n))) modepenalty[n] += weight; + } + } + + void calcmappenalties(); // sum up, how long ago each map+mode was played + servermap *recalcgamesuggestions(int numpl); // regenerate list of playable games (map+mode) + + servermap *imfeelinglucky(int numpl = -1) + { + return recalcgamesuggestions(numpl); + } + + servermap *getcurrent() + { + if(!current) current = imfeelinglucky(); + return current; + } + + servermap *next(bool notify = true, bool nochange = false) // load next maprotation set { #ifndef STANDALONE if(!isdedicated) @@ -857,34 +1014,16 @@ struct servermaprot : serverconfigfile defformatstring(nextmapalias)("nextmap_%s", getclientmap()); const char *map = getalias(nextmapalias); // look up map in the cycle startgame(map && notify ? map : getclientmap(), getclientmode(), -1, notify); - return -1; + return NULL; } #endif - if(configsets.empty()) fatal("maprot unavailable"); - int n = numclients(); - int csl = configsets.length(); - int ccs = curcfgset; - if(ccs >= 0 && ccs < csl) ccs += configsets[ccs].skiplines; - configset *c = NULL; - loopi(3 * csl + 1) - { - ccs++; - if(ccs >= csl || ccs < 0) ccs = 0; - c = &configsets[ccs]; - if((n >= c->minplayer || i >= csl) && (!c->maxplayer || n <= c->maxplayer || i >= 2 * csl)) - { - mapstats *ms = NULL; - if((ms = getservermapstats(c->mapname)) && mapisok(ms)) break; - else logline(ACLOG_INFO, "maprot error: map '%s' %s", c->mapname, (ms ? "does not satisfy some basic requirements" : "not found")); - } - if(i >= 3 * csl) fatal("maprot unusable"); // not a single map in rotation can be found... - } + servermap *s = imfeelinglucky(numclients()); if(!nochange) { - curcfgset = ccs; - startgame(c->mapname, c->mode, c->time, notify); + current = s; + startgame(s->fname, s->bestmode, s->modes[s->bestmode].time, notify); } - return ccs; + return s; } void restart(bool notify = true) // restart current map @@ -896,11 +1035,119 @@ struct servermaprot : serverconfigfile return; } #endif - startgame(smapname, smode, -1, notify); + servermap *s = getcurrent(); + startgame(s->fname, s->bestmode, s->modes[s->bestmode].time, notify); + } +}; + +// generic commented IP list + +struct serveripcclist : serverconfigfile +{ + vector ipranges; + + bool parsecomment(iprangecc &ipr, const char *l) + { + memset(ipr.cc, 0, 4); + l += strspn(l, " "); + loopi(3) if(isprint(l[i])) ipr.cc[i] = l[i]; else break; + return ipr.cc[0] && ipr.cc[1]; } - configset *current() { return configsets.inrange(curcfgset) ? &configsets[curcfgset] : NULL; } - configset *get(int ccs) { return configsets.inrange(ccs) ? &configsets[ccs] : NULL; } + const char *printcomment(iprangecc &ipr) + { + return ipr.cc[3] == '\0' ? ipr.cc : "ERR"; + } + + int cmpcomment(iprangecc &ipr1, iprangecc &ipr2) + { + return ipr1.ci - ipr2.ci; + } + + void clear() + { + ipranges.shrink(0); + } + + void read() + { + iprangecc ir; + int line = 0, errors = 0, concatd = 0; + char *l, *r, *p = buf; + mlog(ACLOG_VERBOSE,"reading ip list '%s'", filename); + while(p < buf + filelen) + { + l = p + strspn(p, " "); p += strlen(p) + 1; line++; + if(!l[0]) continue; + if((r = (char *) atoipr(l, &ir)) && parsecomment(ir, r)) + { + if(ipranges.length() && ipranges.last().ur == ir.lr - 1 && !cmpcomment(ipranges.last(), ir)) + { + ipranges.last().ur = ir.ur; // direct concatenation (for pre-sorted lists) + concatd++; + } + else + ipranges.add(ir); + } + else mlog(ACLOG_INFO," error in line %d, file %s: failed to parse '%s'", line, filename, r ? r : l), errors++; + } + ipranges.sort(cmpiprange); + int orglength = ipranges.length(); + string b1, b2; + loopv(ipranges) + { // make sure, ranges don't overlap - otherwise bsearch gets unpredictable + if(!i) continue; + if(ipranges[i].ur <= ipranges[i - 1].ur) + { + if(cmpcomment(ipranges[i], ipranges[i - 1])) + mlog(ACLOG_INFO," error: IP list entry %s|%s deleted because of overlapping %s|%s", iprtoa(ipranges[i], b1), printcomment(ipranges[i]), iprtoa(ipranges[i - 1], b2), printcomment(ipranges[i - 1])); + else + { + if(ipranges[i].lr == ipranges[i - 1].lr && ipranges[i].ur == ipranges[i - 1].ur) + mlog(ACLOG_VERBOSE," IP list entry %s got dropped (double entry)", iprtoa(ipranges[i], b1)); + else + mlog(ACLOG_VERBOSE," IP list entry %s got dropped (already covered by %s)", iprtoa(ipranges[i], b1), iprtoa(ipranges[i - 1], b2)); + } + ipranges.remove(i--); continue; + } + if(ipranges[i].lr <= ipranges[i - 1].ur) + { + if(!cmpcomment(ipranges[i], ipranges[i - 1])) // same comment + { + mlog(ACLOG_VERBOSE," IP list entries %s and %s are joined due to overlap (both %s)", iprtoa(ipranges[i - 1], b1), iprtoa(ipranges[i], b2), printcomment(ipranges[i])); + ipranges[i - 1].ur = ipranges[i].ur; + ipranges.remove(i--); continue; + } + else + { + mlog(ACLOG_INFO," error: IP list entries %s|%s and %s|%s are overlapping - dropping %s|%s", iprtoa(ipranges[i - 1], b1), printcomment(ipranges[i - 1]), + iprtoa(ipranges[i], b2), printcomment(ipranges[i]), b2, printcomment(ipranges[i])); + errors++; + ipranges.remove(i--); continue; + } + } + if(ipranges[i].lr - 1 == ipranges[i - 1].ur && !cmpcomment(ipranges[i], ipranges[i - 1])) + { + mlog(ACLOG_VERBOSE," concatenating IP list entries %s and %s (both %s)", iprtoa(ipranges[i - 1], b1), iprtoa(ipranges[i], b2), printcomment(ipranges[i])); + ipranges[i - 1].ur = ipranges[i].ur; + ipranges.remove(i--); continue; + } + } + if(logcheck(ACLOG_VERBOSE)) loopv(ipranges) mlog(ACLOG_VERBOSE," %s %s", iprtoa(ipranges[i], b1), printcomment(ipranges[i])); + mlog(ACLOG_INFO,"read %d (%d) IP list entries from '%s', %d errors, %d concatenated", ipranges.length(), orglength, filename, errors, concatd); + } + + const char *check(enet_uint32 ip, bool lock = false) // ip: host byte order + { + if(lock) busy.wait(); + iprangecc t, *res; + t.lr = ip; + t.ur = 0; + res = ipranges.search(&t, cmpipmatch); + const char *cc = res ? printcomment(*res) : NULL; + if(lock) busy.post(); + return cc; + } }; // serverblacklist.cfg @@ -909,16 +1156,17 @@ struct serveripblacklist : serverconfigfile { vector ipranges; + void clear() + { + ipranges.shrink(0); + } + void read() { - if(getfilesize(filename) == filelen) return; - ipranges.shrink(0); - if(!load()) return; - iprange ir; int line = 0, errors = 0; char *l, *r, *p = buf; - logline(ACLOG_VERBOSE,"reading ip blacklist '%s'", filename); + mlog(ACLOG_VERBOSE,"reading ip blacklist '%s'", filename); while(p < buf + filelen) { l = p; p += strlen(p) + 1; line++; @@ -930,33 +1178,33 @@ struct serveripblacklist : serverconfigfile if(l[strspn(l, " ")]) { for(int i = (int)strlen(l) - 1; i > 0 && l[i] == ' '; i--) l[i] = '\0'; - logline(ACLOG_INFO," error in line %d, file %s: ignored '%s'", line, filename, l); + mlog(ACLOG_INFO," error in line %d, file %s: ignored '%s'", line, filename, l); errors++; } } - DELETEA(buf); ipranges.sort(cmpiprange); int orglength = ipranges.length(); + string b1, b2; loopv(ipranges) { if(!i) continue; if(ipranges[i].ur <= ipranges[i - 1].ur) { if(ipranges[i].lr == ipranges[i - 1].lr && ipranges[i].ur == ipranges[i - 1].ur) - logline(ACLOG_VERBOSE," blacklist entry %s got dropped (double entry)", iprtoa(ipranges[i])); + mlog(ACLOG_VERBOSE," blacklist entry %s got dropped (double entry)", iprtoa(ipranges[i], b1)); else - logline(ACLOG_VERBOSE," blacklist entry %s got dropped (already covered by %s)", iprtoa(ipranges[i]), iprtoa(ipranges[i - 1])); + mlog(ACLOG_VERBOSE," blacklist entry %s got dropped (already covered by %s)", iprtoa(ipranges[i], b1), iprtoa(ipranges[i - 1], b2)); ipranges.remove(i--); continue; } if(ipranges[i].lr <= ipranges[i - 1].ur) { - logline(ACLOG_VERBOSE," blacklist entries %s and %s are joined due to overlap", iprtoa(ipranges[i - 1]), iprtoa(ipranges[i])); + mlog(ACLOG_VERBOSE," blacklist entries %s and %s are joined due to overlap", iprtoa(ipranges[i - 1], b1), iprtoa(ipranges[i], b2)); ipranges[i - 1].ur = ipranges[i].ur; ipranges.remove(i--); continue; } } - loopv(ipranges) logline(ACLOG_VERBOSE," %s", iprtoa(ipranges[i])); - logline(ACLOG_INFO,"read %d (%d) blacklist entries from '%s', %d errors", ipranges.length(), orglength, filename, errors); + if(logcheck(ACLOG_VERBOSE)) loopv(ipranges) mlog(ACLOG_VERBOSE," %s", iprtoa(ipranges[i], b1)); + mlog(ACLOG_INFO,"read %d (%d) blacklist entries from '%s', %d errors", ipranges.length(), orglength, filename, errors); } bool check(enet_uint32 ip) // ip: network byte order @@ -982,7 +1230,7 @@ struct servernickblacklist : serverconfigfile vector blacklines; vector blfraglist; - void destroylists() + void clear() { whitelistranges.setsize(0); enumeratek(whitelist, const char *, key, delete key); @@ -993,16 +1241,12 @@ struct servernickblacklist : serverconfigfile void read() { - if(getfilesize(filename) == filelen) return; - destroylists(); - if(!load()) return; - const char *sep = " "; int line = 1, errors = 0; iprchain iprc; blackline bl; char *b, *l, *s, *r, *p = buf; - logline(ACLOG_VERBOSE,"reading nickname blacklist '%s'", filename); + mlog(ACLOG_VERBOSE,"reading nickname blacklist '%s'", filename); while(p < buf + filelen) { l = p; p += strlen(p) + 1; @@ -1054,37 +1298,39 @@ struct servernickblacklist : serverconfigfile bl.line = line; blacklines.add(bl); } - else { logline(ACLOG_INFO," error in line %d, file %s: unknown keyword '%s'", line, filename, l); errors++; } - if(s && s[strspn(s, " ")]) { logline(ACLOG_INFO," error in line %d, file %s: ignored '%s'", line, filename, s); errors++; } + else { mlog(ACLOG_INFO," error in line %d, file %s: unknown keyword '%s'", line, filename, l); errors++; } + if(s && s[strspn(s, " ")]) { mlog(ACLOG_INFO," error in line %d, file %s: ignored '%s'", line, filename, s); errors++; } } line++; } - DELETEA(buf); - logline(ACLOG_VERBOSE," nickname whitelist (%d entries):", whitelist.numelems); - string text; - enumeratekt(whitelist, const char *, key, int, idx, + if(logcheck(ACLOG_VERBOSE)) { - text[0] = '\0'; - for(int i = idx; i >= 0; i = whitelistranges[i].next) + mlog(ACLOG_VERBOSE," nickname whitelist (%d entries):", whitelist.numelems); + string text, b; + enumeratekt(whitelist, const char *, key, int, idx, { - iprchain &ic = whitelistranges[i]; - if(ic.pwd) concatformatstring(text, " pwd:\"%s\"", hiddenpwd(ic.pwd)); - else concatformatstring(text, " %s", iprtoa(ic.ipr)); - } - logline(ACLOG_VERBOSE, " accept %s%s", key, text); - }); - logline(ACLOG_VERBOSE," nickname blacklist (%d entries):", blacklines.length()); - loopv(blacklines) - { - text[0] = '\0'; - loopj(MAXNICKFRAGMENTS) + text[0] = '\0'; + for(int i = idx; i >= 0; i = whitelistranges[i].next) + { + iprchain &ic = whitelistranges[i]; + if(ic.pwd) concatformatstring(text, " pwd:\"%s\"", hiddenpwd(ic.pwd)); + else concatformatstring(text, " %s", iprtoa(ic.ipr, b)); + } + mlog(ACLOG_VERBOSE, " accept %s%s", key, text); + }); + mlog(ACLOG_VERBOSE," nickname blacklist (%d entries):", blacklines.length()); + loopv(blacklines) { - int k = blacklines[i].frag[j]; - if(k >= 0) { concatstring(text, " "); concatstring(text, blfraglist[k]); } + text[0] = '\0'; + loopj(MAXNICKFRAGMENTS) + { + int k = blacklines[i].frag[j]; + if(k >= 0) { concatstring(text, " "); concatstring(text, blfraglist[k]); } + } + mlog(ACLOG_VERBOSE, " %2d block%s%s", blacklines[i].line, blacklines[i].ignorecase ? "i" : "", text); } - logline(ACLOG_VERBOSE, " %2d block%s%s", blacklines[i].line, blacklines[i].ignorecase ? "i" : "", text); } - logline(ACLOG_INFO,"read %d + %d entries from nickname blacklist file '%s', %d errors", whitelist.numelems, blacklines.length(), filename, errors); + mlog(ACLOG_INFO,"read %d + %d entries from nickname blacklist file '%s', %d errors", whitelist.numelems, blacklines.length(), filename, errors); } int checkwhitelist(const client &c) @@ -1189,7 +1435,7 @@ struct serverforbiddenlist : serverconfigfile int num; char entries[100][2][FORBIDDENSIZE+1]; // 100 entries and 2 words per entry is more than enough - void initlist() + void clear() { num = 0; memset(entries,'\0',2*100*(FORBIDDENSIZE+1)); @@ -1213,18 +1459,13 @@ struct serverforbiddenlist : serverconfigfile void read() { - if(getfilesize(filename) == filelen) return; - initlist(); - if(!load()) return; - char *l, *p = buf; - logline(ACLOG_VERBOSE,"reading forbidden list '%s'", filename); + mlog(ACLOG_VERBOSE,"reading forbidden list '%s'", filename); while(p < buf + filelen) { l = p; p += strlen(p) + 1; addentry(l); } - DELETEA(buf); } bool canspeech(char *s) @@ -1271,17 +1512,18 @@ struct serverpasswords : serverconfigfile serverconfigfile::init(name); } + void clear() + { + adminpwds.shrink(staticpasses); + } + void read() { - if(getfilesize(filename) == filelen) return; - adminpwds.shrink(staticpasses); - if(!load()) return; - pwddetail c; const char *sep = " "; int i, line = 1, par[ADMINPWD_MAXPAR]; char *b, *l, *p = buf; - logline(ACLOG_VERBOSE,"reading admin passwords '%s'", filename); + mlog(ACLOG_VERBOSE,"reading admin passwords '%s'", filename); while(p < buf + filelen) { l = p; p += strlen(p) + 1; @@ -1300,13 +1542,12 @@ struct serverpasswords : serverconfigfile c.line = line; c.denyadmin = par[0] > 0; adminpwds.add(c); - logline(ACLOG_VERBOSE,"line%4d: %s %d", c.line, hiddenpwd(c.pwd), c.denyadmin ? 1 : 0); + mlog(ACLOG_VERBOSE,"line%4d: %s %d", c.line, hiddenpwd(c.pwd), c.denyadmin ? 1 : 0); } } line++; } - DELETEA(buf); - logline(ACLOG_INFO,"read %d admin passwords from '%s'", adminpwds.length() - staticpasses, filename); + mlog(ACLOG_INFO,"read %d admin passwords from '%s'", adminpwds.length() - staticpasses, filename); } bool check(const char *name, const char *pwd, int salt, pwddetail *detail = NULL, enet_uint32 address = 0) @@ -1349,78 +1590,544 @@ struct serverpasswords : serverconfigfile #define MAXINFOLINELEN 100 // including color codes -struct serverinfofile +struct serverinfofile : serverconfigfile // plaintext info file, used for serverinfo and motd { - struct serverinfotext { const char *type; char lang[3]; char *info; int lastcheck; }; - vector serverinfotexts; - const char *infobase, *motdbase; + vector msg; + int maxlen; - void init(const char *info, const char *motd) { infobase = info; motdbase = motd; } - - char *readinfofile(const char *fnbase, const char *lang) + void init(const char *fnbase, int ml) { - defformatstring(fname)("%s_%s.txt", fnbase, lang); - path(fname); - int len, n; - char *c, *b, *s, *t, *buf = loadfile(fname, &len); - if(!buf) return NULL; - char *nbuf = new char[len + 2]; - for(t = nbuf, s = strtok_r(buf, "\n\r", &b); s; s = strtok_r(NULL, "\n\r", &b)) - { - c = strstr(s, "//"); - if(c) *c = '\0'; // strip comments - for(n = (int)strlen(s) - 1; n >= 0 && s[n] == ' '; n--) s[n] = '\0'; // strip trailing blanks - filterrichtext(t, s + strspn(s, " "), MAXINFOLINELEN); // skip leading blanks - n = (int)strlen(t); - if(n) t += n + 1; - } - *t = '\0'; - DELETEA(buf); - if(!*nbuf) DELETEA(nbuf); - logline(ACLOG_DEBUG,"read file \"%s\"", fname); - return nbuf; + maxlen = ml; + defformatstring(fname)("%s_en.txt", fnbase); + serverconfigfile::init(fname); } - const char *getinfocache(const char *fnbase, const char *lang) + void clear() { - serverinfotext sn = { fnbase, { 0, 0, 0 }, NULL, 0} , *s = &sn; - filterlang(sn.lang, lang); - if(!sn.lang[0]) return NULL; - loopv(serverinfotexts) - { - serverinfotext &si = serverinfotexts[i]; - if(si.type == s->type && !strcmp(si.lang, s->lang)) - { - if(servmillis - si.lastcheck > (si.info ? 15 : 45) * 60 * 1000) - { // re-read existing files after 15 minutes; search missing again after 45 minutes - DELETEA(si.info); - s = &si; - } - else return si.info; - } - } - s->info = readinfofile(fnbase, lang); - s->lastcheck = servmillis; - if(s == &sn) serverinfotexts.add(*s); - if(fnbase == motdbase && s->info) - { - char *c = s->info; - while(*c) { c += strlen(c); if(c[1]) *c++ = '\n'; } - if(strlen(s->info) > MAXSTRLEN) s->info[MAXSTRLEN] = '\0'; // keep MOTD at sane lengths - } - return s->info; + msg.shrink(0); } - const char *getinfo(const char *lang) + void read() { - return getinfocache(infobase, lang); + string s; + char *l, *p = buf; + mlog(ACLOG_VERBOSE,"reading info text file '%s'", filename); + while(p < buf + filelen) + { + l = p; p += strlen(p) + 1; + ASSERT(MAXINFOLINELEN < MAXSTRLEN); + filtertext(s, l, FTXT__SERVERINFOLINE, MAXINFOLINELEN); + if(*s) cvecprintf(msg, "%s\n", s); + } + if(msg.length() > maxlen) + { + mlog(ACLOG_VERBOSE,"info text file '%s' truncated: %d -> %d", filename, msg.length(), maxlen); + msg.shrink(maxlen); + } } - const char *getmotd(const char *lang) + const char *getmsg() { - const char *motd; - if(*lang && (motd = getinfocache(motdbase, lang))) return motd; - return getinfocache(motdbase, "en"); + if(!msg.length() || msg.last()) msg.add('\0'); // make sure, msg is terminated + return msg.getbuf(); + } + + char *getmsgline(string s, int *pos) + { + s[0] = '\0'; + if(msg.inrange(*pos)) + { + const char *p = getmsg() + *pos; + int len = strcspn(p, "\n") + 1; + copystring(s, p, len); + *pos += len; + } + return s; } }; +// realtime server parameters + +enum { SID_INT, SID_STR }; + +struct servpar +{ + int type; // one of SPAR_* above + const char *name; + union + { + int minval; // SID_INT + int minlen; // SID_STR + }; + union + { + int maxval; // SID_INT + int maxlen; // SID_STR + }; + union + { + int *i; // SID_INT + char *s; // SID_STR + }; + union + { + int *shadow_i; // SID_INT + char *shadow_s; // SID_STR + }; + void (*fun)(); + union + { + const char **list; // SID_INT + const char *defaultstr; // SID_STR + }; + union + { + int defaultint; // SID_INT + int filter; // SID_STR + }; +#ifdef _DEBUG + const char *desc; + int chapter; +#endif + bool logchanges; // log value changes (careful!) + bool fromfile; // true: value can be set by config file, false: value is set by server + bool fromfile_org; // "fromfile" as initially set (a backup to undo manual changes) + + servpar() {} + + // SID_INT + servpar(int type, const char *name, int minval, int maxval, const char *list[], int *i, int *shadow_i, int defval, void (*fun)(), bool logchanges, bool fromfile, const char *_desc) + : type(type), name(name), minval(minval), maxval(maxval), i(i), shadow_i(shadow_i), fun(fun), list(list), defaultint(defval), logchanges(logchanges), fromfile(fromfile), fromfile_org(fromfile) + { DEBUGCODE(desc = _desc); } + + // SID_STR + servpar(int type, const char *name, int minlen, int maxlen, int filter, char *s, char *shadow_s, const char *defaultstr, void (*fun)(), bool logchanges, bool fromfile, const char *_desc) + : type(type), name(name), minlen(minlen), maxlen(maxlen), s(s), shadow_s(shadow_s), fun(fun), defaultstr(defaultstr), filter(filter), logchanges(logchanges), fromfile(fromfile), fromfile_org(fromfile) + { DEBUGCODE(desc = _desc); } + + void update() + { + if(fromfile_org) switch(type) + { + case SID_INT: + { + if(*i != *shadow_i) + { + int tmp = *i; + *i = *shadow_i; + *shadow_i = tmp; // for the duration of fun(), last_parname _is_ actually the old value (at any other time, it's the next value) + if(fun) ((void (__cdecl *)())fun)(); + *shadow_i = *i; + } + break; + } + case SID_STR: + { + if(strcmp(s, shadow_s)) + { + if(fun) + { + char *tmp = new char[maxlen + 1]; + s[maxlen] = shadow_s[maxlen] = '\0'; + strncpy(tmp, s, maxlen + 1); + strncpy(s, shadow_s, maxlen + 1); + strncpy(shadow_s, tmp, maxlen + 1); // for the duration of fun(), last_parname _is_ actually the old value (at any other time, it's the next value) + ((void (__cdecl *)())fun)(); + strncpy(shadow_s, s, maxlen + 1); + delete[] tmp; + } + else strncpy(s, shadow_s, maxlen + 1); + } + break; + } + } + } + + bool nextvalue(char *nv) + { + switch(type) + { + case SID_INT: + { + int ni = ATOI(nv), idx; + if(list && (idx = getlistindex(nv, list, false, -1)) >= 0) ni = idx + minval; // multiple choice + if(ni >= minval && ni <= maxval) { *shadow_i = ni; return true; } + break; + } + case SID_STR: + { + filtertext(nv, nv, filter); + int ni = (int)strlen(nv); + if(ni >= minlen && ni <= maxlen) { strncpy(shadow_s, nv, maxlen + 1); return true; } + break; + } + } + return false; + } +}; + +hashtable *servpars = NULL; + +int addservparint(const char *name, int minval, int cur, int maxval, const char *list[], int *storage, int *shadowstorage, void (*fun)(), bool logchanges, bool fromfile, const char *desc) +{ + if(!servpars) servpars = new hashtable; + if(list && minval == maxval) while(list[maxval - minval + 1][0]) maxval++; // get list length + ASSERT((fromfile && cur >= minval && cur <= maxval) || (!fromfile && minval < ACLOG_NUM)); + servpar v(SID_INT, name, minval, maxval, list, storage, shadowstorage, cur, fun, logchanges, fromfile, desc); + servpars->access(name, v); + *shadowstorage = cur; + return cur; +} + +bool addservparstr(const char *name, int minlen, int maxlen, int filt, const char *cur, char *storage, char *shadowstorage, void (*fun)(), bool logchanges, bool fromfile, const char *desc) +{ + if(!servpars) servpars = new hashtable; + ASSERT(((fromfile && maxlen > minlen && minlen >= 0 && int(strlen(cur)) >= minlen) || (!fromfile && minlen < ACLOG_NUM)) && int(strlen(cur)) <= maxlen); + servpar v(SID_STR, name, minlen, maxlen, filt, storage, shadowstorage, cur, fun, logchanges, fromfile, desc); + servpars->access(name, v); + strncpy(storage, cur, maxlen + 1); + strncpy(shadowstorage, cur, maxlen + 1); + storage[maxlen] = shadowstorage[maxlen] = '\0'; + return true; +} + +struct serverparameter : serverconfigfile +{ + bool load() + { + if(serverconfigfile::load()) + { + int line = 0; + char *b, *l, *p = buf, *kb, *k, *v; + servpar *id; + while(p < buf + filelen) + { + l = p; p += strlen(p) + 1; line++; + l = strtok_r(l, " ", &b); + if(l && (k = strtok_r(l, ":", &kb)) && (v = strtok_r(NULL, ":", &kb)) && (id = servpars->access(k)) && id->fromfile) + { + if(id->type == SID_STR && (v - k) - strlen(k) > 1) filterrichtext(v, v); + id->nextvalue(v); + } + } + return true; + } + return false; + } + + void read() + { // in mainloop: update values from shadow values + enumerate(*servpars, servpar, id, id.update()); + } + + void dump(stream *f) + { + enumerate(*servpars, servpar, id, + switch(id.type) + { + case SID_INT: f->printf("%s:%d // default %d\n", id.name, *id.i, id.defaultint); break; + case SID_STR: f->printf("%s:%s // default %s\n", id.name, id.s, id.defaultstr); break; + } + ); + } + + void log() + { // in mainloop: check for changed values and log if necessary + enumerate(*servpars, servpar, id, + { + if(id.logchanges) switch(id.type) + { + case SID_INT: + { + if(*id.i != *id.shadow_i) + { + if(id.fun) ((void (__cdecl *)())id.fun)(); + mlog(id.minval, "server statistics parameter %s changed from %d to %d", id.name, *id.shadow_i, *id.i); + *id.shadow_i = *id.i; + } + break; + } + case SID_STR: + { + if(strcmp(id.s, id.shadow_s)) + { + id.s[id.maxlen] = id.shadow_s[id.maxlen] = '\0'; + if(id.fun) ((void (__cdecl *)())id.fun)(); + string tmp1; string tmp2; + filtertext(tmp1, id.shadow_s, id.filter); + filtertext(tmp2, id.s, id.filter); + mlog(id.minval, "server statistics parameter %s changed from \"%s\" to \"%s\"", id.name, tmp1, tmp2); + strncpy(id.shadow_s, id.s, id.maxlen + 1); + id.shadow_s[id.maxlen] = '\0'; + } + break; + } + } + }); + } +}; + +// optionally overwrite builtin default values with commandline parameters - before updating from file starts + +void initserverparameter(const char *name, int value) +{ + ASSERT(servpars); + servpar *id = servpars->access(name); + ASSERT(id && id->type == SID_INT); + if(value >= id->minval && value <= id->maxval) + { + *id->shadow_i = value; + id->update(); + } +} + +void initserverparameter(const char *name, const char *value) +{ + ASSERT(servpars); + servpar *id = servpars->access(name); + ASSERT(id && id->type == SID_STR); + string tmp; + filtertext(tmp, value, id->filter); + int len = (int)strlen(tmp); + if(len >= id->minlen && len <= id->maxlen) + { + strncpy(id->shadow_s, value, id->maxlen + 1); + id->update(); + } +} + +void servparallfun() // trigger all functions once, to make sure, unchanged defaults are translated into filtered values +{ + enumerate(*servpars, servpar, id, + { + if(id.fun) ((void (__cdecl *)())id.fun)(); + }); +} + +// write a nice template for serverparameters.cfg + +#ifdef _DEBUG +int siddocsort(servpar **a, servpar **b) { return (*a)->chapter == (*b)->chapter ? strcmp((*a)->name, (*b)->name) : (*a)->chapter - (*b)->chapter; } + +const char *siddocchapters[] = { "dDebug switches", "mMisc settings", "vVote settings", "gMaprot settings", "sServer setup settings", + "CCommandline switch overrides", "DDemo recording settings", "lServer load statistics", "" }; +const char *siddocchaptersorting = "sgDvmdC", *siddocchaptercommentedout = "CD"; + +void serverparameters_dumpdocu(char *fname) +{ + vector sp; + enumerate(*servpars, servpar, id, if(id.desc && id.desc[0]) sp.add(&id)); + loopv(sp) + { + servpar &id = *sp[i]; + const char *e = strchr(siddocchaptersorting, id.desc[0]); + id.chapter = e ? (int) (e - siddocchaptersorting) : id.desc[0]; + if(!id.fromfile) id.chapter |= 1 << 20; + if(!id.logchanges) id.chapter |= 1 << 19; + } + sp.sort(siddocsort); + stream *f = openfile(path(fname), "w"); + int lastchap = 0; + if(f) + { + f->printf("// serverparameters.cfg\n//\n// parameters from this file are read once per minute by the server\n// and are effective immediately\n\n" + "// each setting consists of a keyword and a value, written like this: keyword:value\n// there are no spaces allowed between keyword and value\n" + "// using two colons (keyword::value) activates rich-text filtering for the value\n\n\n"); + loopv(sp) + { + servpar &id = *sp[i]; + if(lastchap != id.desc[0]) + { + int ch = -1; + for(int k = 0; siddocchapters[k][0]; k++) if(siddocchapters[k][0] == id.desc[0]) ch = k; + lastchap = id.desc[0]; + if(ch < 0) f->printf("\n// **** untitled chapter %c ****\n\n", lastchap); + else f->printf("\n// **** %s ****\n\n", siddocchapters[ch] + 1); + } + const char *commentout = strchr(siddocchaptercommentedout, id.desc[0]) != NULL ? "//" : ""; + if(id.fromfile) switch(id.type) + { + case SID_INT: + { + f->printf("// %s %s\n// integer [%d..%d], default %d", id.name, id.desc + 1, id.minval, id.maxval, id.defaultint); + if(id.list) + { + for(int k = 0; id.list[k] && id.list[k][0]; k++) f->printf(", %d:%s", id.minval + k, id.list[k]); + } + f->printf("\n%s%s:%d\n\n", commentout, id.name, id.defaultint); + break; + } + case SID_STR: + { + f->printf("// %s %s\n// string [%d..%d chars], default \"%s\"\n%s%s:%s\n\n", id.name, id.desc + 1, id.minlen, id.maxlen, id.defaultstr, commentout, id.name, id.defaultstr); + break; + } + } + else switch(id.type) // stat parameter + { + case SID_INT: + case SID_STR: + { + f->printf("// %s %s", id.name, id.desc + 1); + if(id.logchanges) f->printf(" (changes logged at level %d)", id.type == SID_INT ? id.minval : id.minlen); + f->printf("\n\n"); + break; + } + } + } + f->printf("\n\n"); + delete f; + } +} +#endif + +// player ID bookkeeping + +const char *vskeywords[VS_NUM + 1] = { "first", "last", "mute", "novote", "ban", "admin", "owner", "mapupload", "minconn", "minact", "flags", "frags", "deaths", "tks", "damage", "ff", "" }; +const char *vsnames[VS_NUM + 1] = { "first login", "last login", "muted until", "novote until", "banned until", "admin until", "owner until", "mapupload until", + "minutes connected", "minutes active", "flags", "frags", "deaths", "teamkills", "damage dealt", "team damage dealt", "" }; +hashtable vitas; + +void vita_s::addname(const char *name) +{ + int rem = 0; + for(; rem < VITANAMEHISTLEN - 1; rem++) if(!strcasecmp(name, namehist[rem])) break; + if(rem) memmove(namehist[1], namehist[0], rem * (MAXNAMELEN + 1)); + copystring(namehist[0], name, MAXNAMELEN + 1); +} + +void vita_s::addip(enet_uint32 ip) +{ + int rem = 0; + for(; rem < VITAIPHISTLEN - 1; rem++) if(iphist[rem] == ip) break; + if(rem) memmove(iphist + 1, iphist, rem * sizeof(ip)); + iphist[0] = ip; +} + +void vita_s::addcc(const char *cc) +{ + int rem = 0; + for(; rem < VITACCHISTLEN - 1; rem++) if(cchist[rem * 2] == cc[0] && cchist[rem * 2 + 1] == cc[1]) break; + if(rem) memmove(cchist + 2, iphist, rem * 2); + loopi(2) cchist[i] = cc[i]; +} + +int parsevitas(char *buf, int filelen) +{ + vita_s v, *vp = &v; + uchar32 pubkey; + string tmp; + enet_uint32 ip; + int res = 0; + char *l, *r, *p = buf, *pe = p + filelen, *b, *o; + while(p < pe) + { + l = p; p += strlen(p) + 1; + l = strtok_r(l, " \t", &b); + while(l && (r = strtok_r(NULL, " \t", &b))) + { + filterrichtext(r, r); + if(!strcmp(l, "PUBKEY")) + { + if(hex2bin(pubkey.u, r, 32) == 32) + { + if(!(vp = vitas.access(pubkey))) + { + memset(&v, 0, sizeof(v)); + vp = &vitas.access(pubkey, v); + } + res++; + } + else vp = &v; // bad pubkey: point to dummy + break; + } + else if(!strcmp(l, "NAME")) + { + filtertext(tmp, r, FTXT__PLAYERNAME, MAXNAMELEN); + if(*tmp) vp->addname(tmp); + } + else if(!strcmp(l, "PUBCOM") || !strcmp(l, "PRIVCOM")) + { + filtertext(tmp, r, FTXT__VITACOMMENT, VITACOMMENTLEN); + copystring(l[1] == 'U' ? vp->publiccomment : vp->privatecomment, tmp, VITACOMMENTLEN + 1); + break; + } + else if(!strcmp(l, "IP")) + { + if(atoip(r, &ip) && ip) vp->addip(ip); + } + else if(!strcmp(l, "CC")) + { + filtercountrycode(r, r); + if(*r != '-') vp->addcc(r); + } + else if(!strcmp(l, "CLAN")) + { + filtertext(tmp, r, FTXT__VITACLAN, VITACLANLEN); + copystring(vp->clan, tmp, VITACLANLEN + 1); + break; + } + else if(!strcmp(l, "STAT")) + { + if((o = strchr(r, ':'))) // key:value + { + *o++ = '\0'; // r is key, o is value + loopk(VS_NUM) if(!strcmp(vskeywords[k], r)) + { + vp->vs[k] = atoi(o); + break; + } + } + } + } + } + return res; +} + +int readvitas(const char *fname) +{ + serverconfigfile vsf; + vsf.init(fname, false); + vsf.load(); + return vsf.buf ? parsevitas(vsf.buf, vsf.filelen) : -1; +} + +void writevitas(stream *f, vector &vs) +{ + string tmp; + vector ctmp; + f->printf("// AC server vita database\n// autosaved %s\n// (don't edit this file, while the server is running)\n\n", timestring(true, "%c", tmp)); + loopvj(vs) + { + const vita_s &v = *vs[j].v; + f->printf("PUBKEY %s", bin2hex(tmp, vs[j].k->u, 32)); + if(v.namehist[0][0]) + { + f->printf("\n\tNAME"); + loopirev(VITANAMEHISTLEN) if(v.namehist[i][0]) f->printf(" %s", escapestring(v.namehist[i], true, true, &ctmp)); + } + if(v.iphist[0]) + { + f->printf("\n\tIP"); + loopirev(VITAIPHISTLEN) if(v.iphist[i]) f->printf(" %s", iptoa(v.iphist[i], tmp)); + } + if(v.privatecomment[0]) f->printf("\n\tPRIVCOM %s", escapestring(v.privatecomment, true, true, &ctmp)); + if(v.publiccomment[0]) f->printf("\n\tPUBCOM %s", escapestring(v.publiccomment, true, true, &ctmp)); + if(v.clan[0]) f->printf("\n\tCLAN %s", escapestring(v.clan, true, true, &ctmp)); + if(v.cchist[0]) + { + f->printf("\n\tCC"); + loopirev(VITACCHISTLEN) if(v.cchist[i * 2]) f->printf(" %c%c", v.cchist[i * 2], v.cchist[i * 2 + 1]); + } + f->printf("\n\tSTAT"); + loopi(VS_NUM) if(v.vs[i]) f->printf(" %s:%d", vskeywords[i], v.vs[i]); + f->printf("\n\n"); + } +} + + + + + + + diff --git a/source/src/serverms.cpp b/source/src/serverms.cpp index 3db4c09a..203310de 100644 --- a/source/src/serverms.cpp +++ b/source/src/serverms.cpp @@ -16,6 +16,8 @@ int connectwithtimeout(ENetSocket sock, const char *hostname, ENetAddress &remot } #endif +extern struct servercommandline scl; +bool usemaster = false; ENetSocket mastersock = ENET_SOCKET_NULL; ENetAddress masteraddress = { ENET_HOST_ANY, ENET_PORT_ANY }, serveraddress = { ENET_HOST_ANY, ENET_PORT_ANY }; string mastername = AC_MASTER_URI; @@ -43,13 +45,11 @@ void disconnectmaster() ENetSocket connectmaster() { - if(!mastername[0]) return ENET_SOCKET_NULL; - extern servercommandline scl; - if(scl.maxclients>MAXCL) { logline(ACLOG_WARNING, "maxclient exceeded: cannot register"); return ENET_SOCKET_NULL; } + if(!mastername[0] || !usemaster) return ENET_SOCKET_NULL; if(masteraddress.host == ENET_HOST_ANY) { - logline(ACLOG_INFO, "looking up %s:%d...", mastername, masterport); + xlog(ACLOG_INFO, "looking up %s:%d...", mastername, masterport); masteraddress.port = masterport; if(!resolverwait(mastername, &masteraddress)) return ENET_SOCKET_NULL; } @@ -61,7 +61,7 @@ ENetSocket connectmaster() } if(sock == ENET_SOCKET_NULL || connectwithtimeout(sock, mastername, masteraddress) < 0) { - logline(ACLOG_WARNING, sock==ENET_SOCKET_NULL ? "could not open socket" : "could not connect"); + xlog(ACLOG_WARNING, sock==ENET_SOCKET_NULL ? "could not open socket" : "could not connect"); return ENET_SOCKET_NULL; } @@ -104,10 +104,10 @@ void processmasterinput() while(args < end && isspace(*args)) args++; if(!strncmp(input, "failreg", cmdlen)) - logline(ACLOG_WARNING, "master server registration failed: %s", args); + xlog(ACLOG_WARNING, "master server registration failed: %s", args); else if(!strncmp(input, "succreg", cmdlen)) { - logline(ACLOG_INFO, "master server registration succeeded"); + xlog(ACLOG_INFO, "master server registration succeeded"); } else processmasterinput(input, cmdlen, args); @@ -160,17 +160,17 @@ void flushmasterinput() else disconnectmaster(); } -extern char *global_name; extern int totalclients; // send alive signal to masterserver after 40 minutes of uptime and if currently in intermission (so theoretically <= 1 hour) // TODO?: implement a thread to drop this "only in intermission" business, we'll need it once AUTH gets active! -static inline void updatemasterserver(int millis, int port) +static inline void updatemasterserver(int millis, int port, int _interm) { - if(!lastupdatemaster || ((millis-lastupdatemaster)>40*60*1000 && (interm || !totalclients))) + if(!lastupdatemaster || ((millis-lastupdatemaster)>40*60*1000 && (_interm || !totalclients))) { - char servername[30]; memset(servername,'\0',30); filtertext(servername, global_name, FTXT__GLOBALNAME, 20); - if(mastername[0]) requestmasterf("regserv %d %s %d\n", port, servername[0] ? servername : "noname", AC_VERSION); + string servdesc; extern string servpubkey; + filtertext(servdesc, scl.servdesc_full[0] ? scl.servdesc_full : "noname", FTXT__GLOBALNAME, 20); + if(mastername[0]) requestmasterf("regserv %d %s-%s %d\n", port, servdesc, scl.ssk ? servpubkey : "nokey", AC_VERSION); lastupdatemaster = millis + 1; } } @@ -178,10 +178,10 @@ static inline void updatemasterserver(int millis, int port) ENetSocket pongsock = ENET_SOCKET_NULL, lansock = ENET_SOCKET_NULL; extern int getpongflags(enet_uint32 ip); -void serverms(int mode, int numplayers, int minremain, char *smapname, int millis, const ENetAddress &localaddr, int *mnum, int *msend, int *mrec, int *cnum, int *csend, int *crec, int protocol_version) +void serverms(int mode, int numplayers, int minremain, char *smapname, int millis, const ENetAddress &localaddr, int *mnum, int *msend, int *mrec, int *cnum, int *csend, int *crec, int protocol_version, const char *servdesccur, int _interm) { flushmasteroutput(); - updatemasterserver(millis, localaddr.port); + updatemasterserver(millis, localaddr.port, _interm); static ENetSocketSet sockset; ENET_SOCKETSET_EMPTY(sockset); @@ -221,14 +221,13 @@ void serverms(int mode, int numplayers, int minremain, char *smapname, int milli if(getint(pi) != 0) // std pong { extern struct servercommandline scl; - extern string servdesc_current; (*mnum)++; *mrec += len; std = true; putint(po, protocol_version); putint(po, mode); putint(po, numplayers); putint(po, minremain); sendstring(smapname, po); - sendstring(servdesc_current, po); + sendstring(servdesccur, po); putint(po, scl.maxclients); putint(po, getpongflags(addr.host)); if(pi.remaining()) @@ -318,6 +317,7 @@ void serverms(int mode, int numplayers, int minremain, char *smapname, int milli } buf.dataLength = len + po.length(); + entropy_add_byte(addr.host ^ (addr.host >> 24)); enet_socket_send(pongsock, &addr, &buf, 1); if(std) *msend += (int)buf.dataLength; else *csend += (int)buf.dataLength; @@ -326,20 +326,22 @@ void serverms(int mode, int numplayers, int minremain, char *smapname, int milli if(mastersock != ENET_SOCKET_NULL && ENET_SOCKETSET_CHECK(sockset, mastersock)) flushmasterinput(); } -// this function should be made better, because it is used just ONCE (no need of so much parameters) -void servermsinit(const char *master, const char *ip, int infoport, bool listen) +void servermsinit(bool listen) { - copystring(mastername, master); + if(scl.master && *scl.master) copystring(mastername, scl.master); + usemaster = !listen || (listen && scl.master && scl.ssk && scl.maxclients <= MAXCLIENTSONMASTER); // true: connect to master, false: LAN-only server + if(scl.maxclients > MAXCLIENTSONMASTER) mlog(ACLOG_WARNING, "maxclient exceeded: cannot register"); disconnectmaster(); if(listen) { - ENetAddress address = { ENET_HOST_ANY, (enet_uint16)infoport }; - if(*ip) + ENetAddress address = { ENET_HOST_ANY, (enet_uint16) CUBE_SERVINFO_PORT(scl.serverport) }; + if(*scl.ip) { - if(enet_address_set_host(&address, ip)<0) logline(ACLOG_WARNING, "server ip not resolved"); + if(enet_address_set_host(&address, scl.ip) < 0) mlog(ACLOG_WARNING, "server ip \"%s\" not resolved", scl.ip); else serveraddress.host = address.host; } + pongsock = enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM); if(pongsock != ENET_SOCKET_NULL && enet_socket_bind(pongsock, &address) < 0) { @@ -348,6 +350,7 @@ void servermsinit(const char *master, const char *ip, int infoport, bool listen) } if(pongsock == ENET_SOCKET_NULL) fatal("could not create server info socket"); else enet_socket_set_option(pongsock, ENET_SOCKOPT_NONBLOCK, 1); + address.port = CUBE_SERVINFO_PORT_LAN; lansock = enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM); if(lansock != ENET_SOCKET_NULL && (enet_socket_set_option(lansock, ENET_SOCKOPT_REUSEADDR, 1) < 0 || enet_socket_bind(lansock, &address) < 0)) @@ -355,7 +358,8 @@ void servermsinit(const char *master, const char *ip, int infoport, bool listen) enet_socket_destroy(lansock); lansock = ENET_SOCKET_NULL; } - if(lansock == ENET_SOCKET_NULL) logline(ACLOG_WARNING, "could not create LAN server info socket"); + if(lansock == ENET_SOCKET_NULL) mlog(ACLOG_WARNING, "could not create LAN server info socket"); else enet_socket_set_option(lansock, ENET_SOCKOPT_NONBLOCK, 1); } + if(!usemaster) mlog(ACLOG_INFO, "server is LAN-only - without connection to masterserver"); } diff --git a/source/src/texture.cpp b/source/src/texture.cpp index 3a2511f3..63ba86bb 100644 --- a/source/src/texture.cpp +++ b/source/src/texture.cpp @@ -516,6 +516,7 @@ Texture *lookuptexture(int tex, Texture *failtex, bool trydl) { defformatstring(pname)("packages/textures/%s", path(s.name, true)); s.tex = textureload(pname, 0, true, true, s.scale, trydl); + if(s.tex != notexture) s.loaded = true; if(!trydl) { if(s.tex==notexture) s.tex = failtex; diff --git a/source/src/tools.cpp b/source/src/tools.cpp index 9a4de70f..131780e3 100644 --- a/source/src/tools.cpp +++ b/source/src/tools.cpp @@ -2,6 +2,8 @@ #include "cube.h" +extern bool isdedicated; + #ifdef NO_POSIX_R char *strtok_r(char *s, const char *delim, char **b) { @@ -15,7 +17,7 @@ char *strtok_r(char *s, const char *delim, char **b) } #endif -string _timestringbuffer; +string _timestringbuffer = ""; const char *timestring(time_t t, bool local, const char *fmt, char *buf) { @@ -79,6 +81,20 @@ int fixmapheadersize(int version, int headersize) // we can't trust hdr.header return headersize; } +header *peekmapheader(uchar *data, int len) // extract the header from an in-memory mapfile (doesn't process all values: only used to get the map revision and timestamp) +{ + static header h; + uLongf rawsize = (int)sizeof(header); + if(uncompress((Bytef*)&h, &rawsize, data, len) == Z_BUF_ERROR && (!strncmp(h.head, "CUBE", 4) || !strncmp(h.head, "ACMP",4))) + { + lilswap(&h.version, 4); // version, headersize, sfactor, numents + if(h.version < 4) memset(&h.waterlevel, 0, sizeof(int) * 16); + else lilswap(&h.maprevision, 4); // maprevision, ambient, flags, timestamp + return &h; + } + return NULL; +} + // map geometry statistics servsqr *createservworld(const sqr *s, int _cubicsize) // create a server-style floorplan on the client, to use same statistics functions on client and server @@ -306,6 +322,7 @@ void calcentitystats(entitystats_s &es, const persistent_entity *pents, int pent es.pickupdistance[r]++; } es.pickups = picks.length(); + es.modes_possible = gmode_possible(es.hasffaspawns, es.hasteamspawns, es.hasflags); } #if 0 const char *rateentitystats(entitystats_s &es) @@ -316,8 +333,6 @@ const char *rateentitystats(entitystats_s &es) } #endif - - int cmpintasc(const int *a, const int *b) { return *a - *b; } // leads to ascending sort order int cmpintdesc(const int *a, const int *b) { return *b - *a; } // leads to descending sort order int stringsort(const char **a, const char **b) { return strcmp(*a, *b); } @@ -325,177 +340,6 @@ int stringsortrev(const char **a, const char **b) { return strcmp(*b, *a); } int stringsortignorecase(const char **a, const char **b) { return strcasecmp(*a, *b); } int stringsortignorecaserev(const char **a, const char **b) { return strcasecmp(*b, *a); } -extern char *maplayout, *testlayout; -extern int maplayout_factor, testlayout_factor, Mvolume, Marea, Mopen, SHhits; -extern float Mheight; -extern int checkarea(int, char *); - -mapstats *loadmapstats(const char *filename, bool getlayout) -{ - const int sizeof_header = sizeof(header), sizeof_baseheader = sizeof_header - sizeof(int) * 16; - static mapstats s; - static uchar *enttypes = NULL; - static short *entposs = NULL; - - DELETEA(enttypes); - loopi(MAXENTTYPES) s.entcnt[i] = 0; - loopi(3) s.spawns[i] = 0; - loopi(2) s.flags[i] = 0; - - stream *f = opengzfile(filename, "rb"); - if(!f) return NULL; - memset(&s.hdr, 0, sizeof_header); - if(f->read(&s.hdr, sizeof_baseheader) != sizeof_baseheader || (strncmp(s.hdr.head, "CUBE", 4) && strncmp(s.hdr.head, "ACMP",4))) { delete f; return NULL; } - lilswap(&s.hdr.version, 4); - s.hdr.headersize = fixmapheadersize(s.hdr.version, s.hdr.headersize); - int restofhead = min(s.hdr.headersize, sizeof_header) - sizeof_baseheader; - if(s.hdr.version > MAPVERSION || s.hdr.numents > MAXENTITIES || - f->read(&s.hdr.waterlevel, restofhead) != restofhead || - !f->seek(clamp(s.hdr.headersize - sizeof_header, 0, MAXHEADEREXTRA), SEEK_CUR)) { delete f; return NULL; } - if(s.hdr.version>=4) - { - lilswap(&s.hdr.waterlevel, 1); - lilswap(&s.hdr.maprevision, 2); - } - else s.hdr.waterlevel = -100000; - entity e; - enttypes = new uchar[s.hdr.numents]; - entposs = new short[s.hdr.numents * 3]; - loopi(s.hdr.numents) - { - f->read(&e, s.hdr.version < 10 ? 12 : sizeof(persistent_entity)); - lilswap((short *)&e, 4); - transformoldentitytypes(s.hdr.version, e.type); - if(e.type == PLAYERSTART && (e.attr2 == 0 || e.attr2 == 1 || e.attr2 == 100)) s.spawns[e.attr2 == 100 ? 2 : e.attr2]++; - if(e.type == CTF_FLAG && (e.attr2 == 0 || e.attr2 == 1)) { s.flags[e.attr2]++; s.flagents[e.attr2] = i; } - s.entcnt[e.type]++; - enttypes[i] = e.type; - entposs[i * 3] = e.x; entposs[i * 3 + 1] = e.y; entposs[i * 3 + 2] = e.z + e.attr1; - } - DELETEA(testlayout); - int minfloor = 0; - int maxceil = 0; - if(s.hdr.sfactor <= LARGEST_FACTOR && s.hdr.sfactor >= SMALLEST_FACTOR) - { - testlayout_factor = s.hdr.sfactor; - int layoutsize = 1 << (testlayout_factor * 2); - bool fail = false; - testlayout = new char[layoutsize + 256]; - memset(testlayout, 0, layoutsize * sizeof(char)); - char *t = NULL; - char floor = 0, ceil; - int diff = 0; - Mvolume = Marea = SHhits = 0; - loopk(layoutsize) - { - char *c = testlayout + k; - int type = f->getchar(); - int n = 1; - switch(type) - { - case 255: - { - if(!t || (n = f->getchar()) < 0) { fail = true; break; } - memset(c, *t, n); - k += n - 1; - break; - } - case 254: // only in MAPVERSION<=2 - if(!t) { fail = true; break; } - *c = *t; - f->getchar(); f->getchar(); - break; - default: - if(type<0 || type>=MAXTYPE) { fail = true; break; } - floor = f->getchar(); - ceil = f->getchar(); - if(floor >= ceil && ceil > -128) floor = ceil - 1; // for pre 12_13 - diff = ceil - floor; - if(type == FHF) floor = -128; - if(floor!=-128 && floormaxceil) maxceil = ceil; - f->getchar(); f->getchar(); - if(s.hdr.version>=2) f->getchar(); - if(s.hdr.version>=5) f->getchar(); - - case SOLID: - *c = type == SOLID ? 127 : floor; - f->getchar(); f->getchar(); - if(s.hdr.version<=2) { f->getchar(); f->getchar(); } - break; - } - if ( type != SOLID && diff > 6 ) - { - // Lucas (10mar2013): Removed "pow2" because it was too strict - if (diff > MAXMHEIGHT) SHhits += /*pow2*/(diff-MAXMHEIGHT)*n; - Marea += n; - Mvolume += diff * n; - } - if(fail) break; - t = c; - } - if(fail) { DELETEA(testlayout); } - else - { - Mheight = Marea ? (float)Mvolume/Marea : 0; - Mopen = checkarea(testlayout_factor, testlayout); - } - } - if(getlayout) - { - DELETEA(maplayout); - if (testlayout) - { - maplayout_factor = testlayout_factor; - extern int maplayoutssize; - maplayoutssize = 1 << testlayout_factor; - int layoutsize = 1 << (testlayout_factor * 2); - maplayout = new char[layoutsize + 256]; - memcpy(maplayout, testlayout, layoutsize * sizeof(char)); - -/* memset(&mapdims, 0, sizeof(struct mapdim_s)); - mapdims.x1 = mapdims.y1 = maplayoutssize; - loopk(layoutsize) if (testlayout[k] != 127) - { - int cwx = k%maplayoutssize, - cwy = k/maplayoutssize; - if(cwx < mapdims.x1) mapdims.x1 = cwx; - if(cwy < mapdims.y1) mapdims.y1 = cwy; - if(cwx > mapdims.x2) mapdims.x2 = cwx; - if(cwy > mapdims.y2) mapdims.y2 = cwy; - } - mapdims.xspan = mapdims.x2 - mapdims.x1; - mapdims.yspan = mapdims.y2 - mapdims.y1; - mapdims.minfloor = minfloor; - mapdims.maxceil = maxceil; -*/ } - } - delete f; - s.hasffaspawns = s.spawns[2] > 0; - s.hasteamspawns = s.spawns[0] > 0 && s.spawns[1] > 0; - s.hasflags = s.flags[0] > 0 && s.flags[1] > 0; - s.enttypes = enttypes; - s.entposs = entposs; - s.cgzsize = getfilesize(filename); - return &s; -} - - - - - - - - - - - - - - - - - ///////////////////////// debugging /////////////////////// #if defined(WIN32) && !defined(_DEBUG) && !defined(__GNUC__) @@ -625,7 +469,12 @@ const char *atoip(const char *s, enet_uint32 *ip) { unsigned int d[4]; int n; - if(!s || sscanf(s, "%u.%u.%u.%u%n", d, d + 1, d + 2, d + 3, &n) != 4) return NULL; + if(!s) return NULL; + if(sscanf(s, "%u.%u.%u.%u%n", d, d + 1, d + 2, d + 3, &n) != 4) + { + *ip = strtoul(s, (char **)&s, 0); // try single-integer IPs + return *ip > 0xffffff ? s : NULL; // require first octet to be non-zero to avoid misinterpreting faulty dotted IPs + } *ip = 0; loopi(4) { @@ -656,23 +505,72 @@ const char *atoipr(const char *s, iprange *ir) return s; } -const char *iptoa(const enet_uint32 ip) +const char *iptoa(const enet_uint32 ip, char *b) { - static string s[2]; - static int buf = 0; - buf = (buf + 1) % 2; - formatstring(s[buf])("%d.%d.%d.%d", (ip >> 24) & 255, (ip >> 16) & 255, (ip >> 8) & 255, ip & 255); - return s[buf]; + formatstring(b)("%d.%d.%d.%d", (ip >> 24) & 255, (ip >> 16) & 255, (ip >> 8) & 255, ip & 255); + return b; } -const char *iprtoa(const struct iprange &ipr) +const char *iprtoa(const struct iprange &ipr, char *b) { - static string s[2]; - static int buf = 0; - buf = (buf + 1) % 2; - if(ipr.lr == ipr.ur) copystring(s[buf], iptoa(ipr.lr)); - else formatstring(s[buf])("%s-%s", iptoa(ipr.lr), iptoa(ipr.ur)); - return s[buf]; + if(ipr.lr == ipr.ur) return iptoa(ipr.lr, b); + else formatstring(b)("%d.%d.%d.%d-%d.%d.%d.%d", (ipr.lr >> 24) & 255, (ipr.lr >> 16) & 255, (ipr.lr >> 8) & 255, ipr.lr & 255, (ipr.ur >> 24) & 255, (ipr.ur >> 16) & 255, (ipr.ur >> 8) & 255, ipr.ur & 255); + return b; +} + +char *formatdemofilename(const char *demoformat, const char *timestampformat, const char *map, int mode, int srvclock, int secondsplayed, int secondsremaining, enet_uint32 ip, char *buf) +{ + vector d; + // we use the following internal mapping of formatchars: + // %g : gamemode (int) %G : gamemode (chr) %F : gamemode (full) + // %m : minutes remaining %M : minutes played + // %s : seconds remaining %S : seconds played + // %h : IP of server %H : hostname of server (client only) + // %n : mapName + // %w : timestamp "when" + bool did_map = false, did_mode = false, did_ts = false; + for(const char *s = demoformat; *s; s++) + { + if(*s == '%') + { + switch(*++s) + { + case 'F': cvecconcat(d, fullmodestr(mode)); did_mode = true; break; + case 'g': cvecprintf(d, "%d", mode); did_mode = true; break; + case 'G': cvecconcat(d, acronymmodestr(mode)); did_mode = true; break; + case 'H': + if(!isdedicated) + { +#ifndef STANDALONE + ENetAddress a = { htonl(ip), 0 }; + cvecconcat(d, !enet_address_get_host(&a, buf, MAXSTRLEN) ? buf : "unknown"); + break; +#endif + } // fallthrough to "IP" on dedicated servers + case 'h': cvecconcat(d, ip ? iptoa(ip, buf) : "local"); break; + case 'm': cvecprintf(d, "%d", secondsremaining / 60); break; + case 'M': cvecprintf(d, "%d", secondsplayed / 60); break; + case 'n': cvecconcat(d, map); did_map = true; break; + case 's': cvecprintf(d, "%d", secondsremaining); break; + case 'S': cvecprintf(d, "%d", secondsplayed); break; + case 'w': + { + time_t t = ((time_t) srvclock) * 60; + bool utc = *timestampformat == 'U'; + cvecconcat(d, timestring(t, !utc, timestampformat + int(utc), buf)); + did_ts = true; + break; + } + default: + if(*s) d.add(*s); + break; + } + } + else d.add(*s); + } + d.add('\0'); + filtertext(buf, d.getbuf(), FTXT__DEMONAME); + return did_map && did_mode && did_ts ? buf : formatdemofilename("%w_%h_%n_%G", "%Y%m%d_%H%M", map, mode, srvclock, secondsplayed, secondsremaining, ip, buf); } int cmpiprange(const struct iprange *a, const struct iprange *b) @@ -701,6 +599,13 @@ int cvecprintf(vector &v, const char *s, ...) return len; } +int cvecconcat(vector &v, const char *s) +{ + int len = strlen(s); + if(len) v.put(s, len); + return len; +} + const char *hiddenpwd(const char *pwd, int showchars) { static int sc = 3; @@ -714,7 +619,7 @@ const char *hiddenpwd(const char *pwd, int showchars) int getlistindex(const char *key, const char *list[], bool acceptnumeric, int deflt) { int max = 0; - while(list[max][0]) if(!strcasecmp(key, list[max])) return max; else max++; + while(list[max] && list[max][0]) if(!strcasecmp(key, list[max])) return max; else max++; if(acceptnumeric && isdigit(key[0])) { int i = (int)strtol(key, NULL, 0); @@ -722,7 +627,7 @@ int getlistindex(const char *key, const char *list[], bool acceptnumeric, int de } #if !defined(STANDALONE) && defined(_DEBUG) char *opts = conc(list, -1, true); - if(*key) clientlogf("warning: unknown token \"%s\" (not in list [%s])", key, opts); + if(!isdedicated && *key) clientlogf("warning: unknown token \"%s\" (not in list [%s])", key, opts); delstring(opts); #endif return deflt; diff --git a/source/src/tools.h b/source/src/tools.h index fc454db6..ba038057 100644 --- a/source/src/tools.h +++ b/source/src/tools.h @@ -66,6 +66,7 @@ inline int iabs(int n) { return labs(n); } #define rnd(x) ((int)(randomMT()&0xFFFFFF)%(x)) #define rndscale(x) (float((randomMT()&0xFFFFFF)*double(x)/double(0xFFFFFF))) #define detrnd(s, x) ((int)(((((uint)(s))*1103515245+12345)>>16)%(x))) +#define constarraysize(x) ((int)(sizeof(x)/sizeof(*x))) #define loop(v,m) for(int v = 0; v MAXMAPNAMELEN) return false; - if(len == 3 || len == 4) - { - char uc[4]; - loopi(3) uc[i] = toupper(s[i]); - uc[3] = '\0'; - const char *resd = "COMLPTCONPRNAUXNUL", *fnd = strstr(resd, uc); - if(fnd) - { - int pos = (int) (fnd - resd); - if(pos == 0 || pos == 3) - { - if(isdigit(s[3])) return false; // COMx, LPTx - } - else if(pos % 3 == 0) return false; // CON, PRN, AUX, NUL - } - } - while(*s != '\0') - { - if(!isalnum(*s) && *s != '_' && *s != '-' && *s != '.') return false; - ++s; - } - return true; -} +#define defvformatstring(d,last,fmt) string d; vformatstring(d,last,fmt) #define loopv(v) for(int i = 0; i<(v).length(); i++) #define loopvj(v) for(int j = 0; j<(v).length(); j++) @@ -542,14 +511,16 @@ static inline bool htcmp(const char *x, const char *y) return !strcmp(x, y); } -static inline uint hthash(const uchar *key) +struct uchar32 { uchar u[32]; }; + +static inline uint hthash(const uchar32 &key) { - return *((uint *)key); + return *((uint *)&key); } -static inline bool htcmp(const uchar *x, const uchar *y) // assume "uchar *" points to 32byte public keys +static inline bool htcmp(const uchar32 &x, const uchar32 &y) { - return !memcmp(x, y, 32); + return !memcmp(&x, &y, 32); } static inline uint hthash(int key) @@ -626,10 +597,10 @@ template struct hashtable } chain *c = unused; unused = unused->next; - c->key = key; c->next = table[h]; - table[h] = c; + c->key = key; numelems++; + table[h] = c; return c; } @@ -879,14 +850,6 @@ template inline void endiansame(T *buf, int len) {} template inline void bigswap(T *buf, int len) { if(*(const uchar *)&islittleendian) endianswap(buf, len); } #endif -#define uint2ip(address, ip) uchar ip[4]; \ -if(isbigendian())\ -{ \ - enet_uint32 big = endianswap(address);\ - memcpy(&ip, &big, 4);\ -}\ -else memcpy(&ip, &address, 4);\ - /* workaround for some C platforms that have these two functions as macros - not used anywhere */ #ifdef getchar #undef getchar @@ -974,11 +937,9 @@ extern void *zipmanualopen(stream *f, vector &files); extern stream *zipmanualstream(void *a, int n); extern int zipmanualread(void *a, int n, stream *f, int maxlen = INT_MAX); extern void zipmanualclose(void *a); -extern struct mapstats *loadmapstats(const char *filename, bool getlayout); extern bool cmpb(void *b, int n, enet_uint32 c); extern bool cmpf(char *fn, enet_uint32 c); extern enet_uint32 adler(unsigned char *data, size_t len); -extern void endianswap(void *, int, int); extern bool isbigendian(); extern void strtoupper(char *t, const char *s = NULL); extern void seedMT(uint seed); @@ -991,13 +952,16 @@ extern void entropy_add_block(const uchar *s, int len); extern void entropy_get(uchar *buf, int len); struct iprange { enet_uint32 lr, ur; }; +struct iprangecc : iprange { union { int ci; char cc[4]; enet_uint32 cu; }; }; extern const char *atoip(const char *s, enet_uint32 *ip); extern const char *atoipr(const char *s, iprange *ir); -extern const char *iptoa(const enet_uint32 ip); -extern const char *iprtoa(const struct iprange &ipr); +extern const char *iptoa(const enet_uint32 ip, char *b); +extern const char *iprtoa(const struct iprange &ipr, char *b); +extern char *formatdemofilename(const char *demoformat, const char *timestampformat, const char *map, int mode, int srvclock, int secondsplayed, int secondsremaining, enet_uint32 ip, char *buf); extern int cmpiprange(const struct iprange *a, const struct iprange *b); extern int cmpipmatch(const struct iprange *a, const struct iprange *b); extern int cvecprintf(vector &v, const char *s, ...) PRINTFARGS(2, 3); +extern int cvecconcat(vector &v, const char *s); extern const char *hiddenpwd(const char *pwd, int showchars = 0); extern int getlistindex(const char *key, const char *list[], bool acceptnumeric = true, int deflt = -1); extern void parseupdatelist(hashtable &ht, char *buf, const char *prefix = NULL, const char *suffix = NULL); @@ -1023,7 +987,7 @@ struct sl_semaphore sl_semaphore(int init, int *errorcount); // init: initial semaphore value; errorcount: pointer to error counter for semaphore-related errors or NULL ~sl_semaphore(); void wait(); // blocks, until semaphore gets available - int trywait(); // returns 0, if semaphore was locked (like wait(), but returns !=0 instead of blocking) + int trywait(); // returns 0, if semaphore was successfully locked (like wait(), but returns !=0 instead of blocking) int timedwait(int howlongmillis); // like trywait(), but waits for a litte before returning failure int getvalue(); // returns current semaphore value void post(); // increments (unlocks) semaphore @@ -1036,5 +1000,19 @@ extern void sl_detachthread(void *ti); extern void sl_sleep(int duration); extern bool ismainthread(); -#endif +///////////////////////////////////////////////////////// FNV-1a /////////////////////////////////////////////////////////////////// +// Fowler–Noll–Vo is a non-cryptographic hash function created by Glenn Fowler, Landon Curt Noll, and Phong Vo. It is public domain. +// We use the 32-bit-version here. +inline void fnv1a_init(uint32_t &hash) +{ + hash = 2166136261UL; +} + +inline void fnv1a_add(uint32_t &hash, uchar byte) +{ + hash ^= byte; + hash *= 16777619; +} + +#endif diff --git a/source/src/vertmodel.h b/source/src/vertmodel.h index ac9e1bcd..8b0464d8 100644 --- a/source/src/vertmodel.h +++ b/source/src/vertmodel.h @@ -1256,7 +1256,6 @@ struct vertmodel : model void setskin(int tex = 0) { - //if(parts.length()!=1 || parts[0]->meshes.length()!=1) return; if(parts.length() < 1 || parts[0]->meshes.length() < 1) return; mesh &m = *parts[0]->meshes[0]; m.tex = tex; diff --git a/source/src/weapon.cpp b/source/src/weapon.cpp index d912f36a..2065c3b5 100644 --- a/source/src/weapon.cpp +++ b/source/src/weapon.cpp @@ -5,7 +5,6 @@ #include "hudgun.h" VARP(autoreload, 0, 1, 1); -VARP(akimboautoswitch, 0, 1, 1); VARP(akimboendaction, 0, 3, 3); // 0: switch to knife, 1: stay with pistol (if has ammo), 2: switch to grenade (if possible), 3: switch to primary (if has ammo) - all fallback to previous one w/o ammo for target struct sgray { @@ -15,7 +14,7 @@ struct sgray { sgray sgr[SGRAYS*3]; -int burstshotssettings[NUMGUNS] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; +int burstshotssettings[NUMGUNS] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, }; void updatelastaction(playerent *d, int millis = lastmillis) { @@ -869,7 +868,7 @@ VARP(accuracy,0,0,1); void r_accuracy(int h) { int i = player1->weaponsel->type; - if(accuracy && valid_weapon(i) && i != GUN_CPISTOL) + if(accuracy && valid_weapon(i)) { int x_offset = 2 * HUDPOS_X_BOTTOMLEFT, y_offset = 2 * (h - 1.75 * FONTH); string line; @@ -890,7 +889,7 @@ void r_accuracy(int h) void accuracyinfo() { vector lines; - loopi(NUMGUNS) if(i != GUN_CPISTOL && accuracym[i].shots) + loopi(NUMGUNS) if(accuracym[i].shots) { float acc = 100.0 * accuracym[i].hits / (float)accuracym[i].shots; string line; @@ -934,7 +933,6 @@ int weapon::flashtime() const { return max((int)info.attackdelay, 120)/4; } void weapon::sendshoot(vec &from, vec &to, int millis) { if(owner!=player1) return; - owner->shoot = true; addmsg(SV_SHOOT, "ri2i3iv", millis, owner->weaponsel->type, (int)(to.x*DMF), (int)(to.y*DMF), (int)(to.z*DMF), hits.length(), hits.length()*sizeof(hitmsg)/sizeof(int), hits.getbuf()); @@ -1077,7 +1075,6 @@ void weapon::equipplayer(playerent *pl) pl->weapons[GUN_GRENADE] = new grenades(pl); pl->weapons[GUN_KNIFE] = new knife(pl); pl->weapons[GUN_PISTOL] = new pistol(pl); - pl->weapons[GUN_CPISTOL] = new cpistol(pl); pl->weapons[GUN_CARBINE] = new carbine(pl); pl->weapons[GUN_SHOTGUN] = new shotgun(pl); pl->weapons[GUN_SNIPER] = new sniperrifle(pl); @@ -1176,7 +1173,7 @@ void grenadeent::_throw(const vec &from, const vec &vel) inwater = waterlevel > o.z; if(local) { - addmsg(SV_THROWNADE, "ri7", int(o.x*DMF), int(o.y*DMF), int(o.z*DMF), int(vel.x*DMF), int(vel.y*DMF), int(vel.z*DMF), lastmillis-millis); + addmsg(SV_THROWNADE, "ri7", int(o.x*DMF), int(o.y*DMF), int(o.z*DMF), int(vel.x*DNF), int(vel.y*DNF), int(vel.z*DNF), lastmillis-millis); audiomgr.playsound(S_GRENADETHROW, SP_HIGH); } else audiomgr.playsound(S_GRENADETHROW, owner); @@ -1554,111 +1551,6 @@ int assaultrifle::dynspread() { return shots > 2 ? 55 : ( info.spread + ( shots float assaultrifle::dynrecoil() { return info.recoil + (rnd(8)*-0.01f); } bool assaultrifle::selectable() { return weapon::selectable() && !m_noprimary && this == owner->primweap; } -// combat pistol - -cpistol::cpistol(playerent *owner) : gun(owner, GUN_CPISTOL), bursting(false) {} -bool cpistol::selectable() { return false; /*return weapon::selectable() && !m_noprimary && this == owner->primweap;*/ } -void cpistol::onselecting() { weapon::onselecting(); bursting = false; } -void cpistol::ondeselecting() { bursting = false; } -bool cpistol::reload(bool autoreloaded) -{ - bool r = weapon::reload(autoreloaded); - if(owner==player1 && r) { bursting = false; } - return r; -} - -bool burst = false; -int burstshots = 0; - -bool cpistol::attack(vec &targ) // modded from gun::attack // FIXME -{ - int attackmillis = lastmillis-owner->lastaction - gunwait; - if(attackmillis<0) return false; - gunwait = reloading = 0; - - if (bursting) burst = true; - - if(!owner->attacking) - { - shots = 0; - checkautoreload(); - return false; - } - - attackmillis = lastmillis - min(attackmillis, curtime); - updatelastaction(owner, attackmillis); - if(!mag) - { - audiomgr.playsoundc(S_NOAMMO); - gunwait += 250; - owner->lastattackweapon = NULL; - shots = 0; - checkautoreload(); - return false; - } - - owner->lastattackweapon = this; - shots++; - if (burst) burstshots++; - - if(!burst) owner->attacking = false; - - vec from = owner->o; - vec to = targ; - from.z -= weaponbeloweye; - - attackphysics(from, to); - - hits.setsize(0); - raydamage(from, to, owner); - attackfx(from, to, 0); - - if ( burst && burstshots > 2 ) - { - gunwait = 500; - burstshots = 0; - burst = owner->attacking = false; - } - else if ( burst ) - { - gunwait = 80; - } - else - { - gunwait = info.attackdelay; - } - mag--; - - sendshoot(from, to, attackmillis); - return true; -} - -void cpistol::setburst(bool enable) -{ - if(this == owner->weaponsel && !reloading && owner->state == CS_ALIVE) - { - bursting = enable; - } -} - -void setburst(bool enable) -{ - if(player1->weaponsel->type != GUN_CPISTOL) return; - if(intermission) return; - cpistol *cp = (cpistol *)player1->weaponsel; - cp->setburst(enable); - if (!burst) - { - if ( enable && burstshots == 0 ) attack(true); - } - else - { - if ( burstshots == 0 ) burst = player1->attacking = enable; - } -} - -COMMAND(setburst, "d"); - // pistol pistol::pistol(playerent *owner) : gun(owner, GUN_PISTOL) {} diff --git a/source/src/weapon.h b/source/src/weapon.h index ab8ebf57..b69ea3ab 100644 --- a/source/src/weapon.h +++ b/source/src/weapon.h @@ -143,18 +143,6 @@ struct assaultrifle : gun bool selectable(); }; -struct cpistol : gun -{ - bool bursting; - cpistol(playerent *owner); - bool reload(bool autoreloaded); - bool selectable(); - void onselecting(); - void ondeselecting(); - bool attack(vec &targ); - void setburst(bool enable); -}; - struct pistol : gun { pistol(playerent *owner); diff --git a/source/src/world.cpp b/source/src/world.cpp index 873e0896..d72856a6 100644 --- a/source/src/world.cpp +++ b/source/src/world.cpp @@ -703,9 +703,7 @@ bool empty_world(int factor, bool force) // main empty world creation routine if(clearmap && unsavededits) { xmapbackup("newmap", ""); unsavededits = 0; } if(copy) ow = blockcopy(shrink ? bs : be); - extern char *mlayout; DELETEA(world); - DELETEA(mlayout); setupworld(factor); loop(x,ssize) loop(y,ssize) @@ -1050,3 +1048,10 @@ void mapareacheck(char *what) // "vdelta" | "steepest" | "total" | "pprest" | "p COMMAND(mapareacheck, "s"); +void showmapstats() +{ + conoutf("\f2Map quality stats"); + conoutf("this map %s", rnd(3) ? "is great" : "sucks"); +} +COMMAND(showmapstats, ""); + diff --git a/source/src/world.h b/source/src/world.h index 39c2c2b8..13e5c666 100644 --- a/source/src/world.h +++ b/source/src/world.h @@ -97,21 +97,6 @@ struct _mapconfigdata } }; -struct mapstats -{ - struct header hdr; - int entcnt[MAXENTTYPES]; - int cgzsize; - uchar *enttypes; - short *entposs; - int spawns[3]; - int flags[2]; - int flagents[2]; - bool hasffaspawns; - bool hasteamspawns; - bool hasflags; -}; - struct mapdim_s { // 0 2 1 3 6 7 int x1, x2, y1, y2, minfloor, maxceil; // outer borders (points to last used cube) @@ -183,10 +168,3 @@ struct entitystats_s short first[MAXENTTYPES], last[MAXENTTYPES]; // first and last occurence of every basic ent type }; - -// legacy - -#define MAXMHEIGHT 30 -#define MAXMAREA 10000 -#define MAXHHITS 50000 // was 6000, which denied my fav. maps - jamz, 2012-06-12; 15000 denies sane map too - lucas -#define MINFF 2500 diff --git a/source/src/worldio.cpp b/source/src/worldio.cpp index 7100f882..db1d133f 100644 --- a/source/src/worldio.cpp +++ b/source/src/worldio.cpp @@ -7,8 +7,9 @@ VARP(worldiodebug, 0, 0, 1); static string cgzname, ocgzname, bakname, cbakname, mcfname, omcfname; -const char *setnames(const char *name) +const char **setnames(const char *name) { + static const char *paths[2] = { ocgzname, cgzname }; const char *mapname = behindpath(name), *nt = numtime(); formatstring(cgzname) ("packages" PATHDIVS "maps" PATHDIVS "%s.cgz", mapname); formatstring(mcfname) ("packages" PATHDIVS "maps" PATHDIVS "%s.cfg", mapname); @@ -16,7 +17,7 @@ const char *setnames(const char *name) formatstring(omcfname)("packages" PATHDIVS "maps" PATHDIVS "official" PATHDIVS "%s.cfg", mapname); formatstring(bakname) ("packages" PATHDIVS "maps" PATHDIVS "%s_%s.BAK", mapname, nt); formatstring(cbakname)("packages" PATHDIVS "maps" PATHDIVS "%s.cfg_%s.BAK", mapname, nt); - return cgzname; + return paths; } // the optimize routines below are here to reduce the detrimental effects of messy mapping by @@ -39,7 +40,7 @@ void voptimize() // reset vdeltas on non-hf cubes // these two are used by getmap/sendmap.. transfers compressed maps directly -void writemap(char *name, int msize, uchar *mdata) +void writemap(const char *name, int msize, uchar *mdata) { setnames(name); backup(cgzname, bakname); @@ -50,17 +51,17 @@ void writemap(char *name, int msize, uchar *mdata) conoutf("wrote map %s as file %s", name, cgzname); } -uchar *readmap(char *name, int *size, int *revision) +uchar *readmap(const char *name, int *size, int *revision) { setnames(name); uchar *data = (uchar *)loadfile(cgzname, size); if(!data) { conoutf("\f3could not read map %s", cgzname); return NULL; } - mapstats *ms = loadmapstats(cgzname, false); - if(revision) *revision = ms->hdr.maprevision; + header *h = peekmapheader(data, *size); + if(revision) *revision = h ? h->maprevision : 0; return data; } -void writecfggz(char *name, int size, int sizegz, uchar *data) +void writecfggz(const char *name, int size, int sizegz, uchar *data) { if(size < 1 || !sizegz || size > MAXCFGFILESIZE) return; setnames(name); @@ -86,7 +87,7 @@ void writecfggz(char *name, int size, int sizegz, uchar *data) #define GZBUFSIZE ((MAXCFGFILESIZE * 11) / 10) -uchar *readmcfggz(char *name, int *size, int *sizegz) +uchar *readmcfggz(const char *name, int *size, int *sizegz) { setnames(name); uchar *gzbuf = new uchar[GZBUFSIZE]; @@ -282,7 +283,7 @@ void unpackheaderextra(uchar *buf, int len) // break the extra data from the ma while(1) { int len = getuint(p), flags = getuint(p); - if(p.overread() || len > p.remaining()) break; + if(p.overread() || len > p.remaining() || len < 0 || flags < 0) break; DEBUGCODE(clientlogf(" found headerextra \"%s\", %d bytes%s", hx_name(flags & HX_TYPEMASK), len, flags & HX_FLAG_PERSIST ? ", persistent" : "")); // debug info headerextras.add(new headerextra(len, flags, p.subbuf(len).buf)); } @@ -305,7 +306,8 @@ void parseheaderextra(bool clearnonpersist = true, int ignoretypes = 0) // pars case HX_CONFIG: setcontext("map", "embedded"); - execute((const char *)q.buf); // needs to have '\0' at the end + if(headerextras[i]->len > 0 && headerextras[i]->data[headerextras[i]->len - 1] == '\0') execute((const char *)q.buf); // needs to have '\0' at the end, better check... + else conoutf("malformed emb config"); resetcontext(); break; @@ -540,6 +542,111 @@ bool gotovantagepoint() } COMMANDF(gotovantagepoint, "", () { intret((editmode || (!multiplayer(NULL) && player1->isspectating())) && gotovantagepoint() ? 1 : 0); }); +void modeinfo(char *mode, char *info) +{ + char *modeinfolines[GMODE_NUM] = { NULL }; + string line, tmp; + + // find and unpack HX_MODEINFO + int he = findheaderextra(HX_MODEINFO); + if(he >= 0) + { + ucharbuf q(headerextras[he]->data, headerextras[he]->len); + for(int mode = getuint(q); !q.overread() && mode > 0; mode = getuint(q)) + { + getstring(line, q, MAXSTRLEN); + filterconfigset(line); + loopi(GMODE_NUM) if((mode & (1 << i)) && !modeinfolines[i]) modeinfolines[i] = newstring(line); + } + } + + // parse command arguments and apply changes or generate output + vector res; + copystring(line, info); + filterconfigset(line); + int modes = gmode_parse(mode), n = 0; + if(!strcmp(mode, "*") || !strcmp(mode, "all")) modes = GMMASK__MPNOCOOP; + else if(!strcmp(mode, "list")) + { + loopi(GMODE_NUM) if(modeinfolines[i] && ++n) conoutf("%s: %s", gmode_enum(1 << i, tmp), escapestring(modeinfolines[i])); + if(!n) conoutf("no modeinfo lines set"); + } + else if(!strcmp(mode, "get")) + { + loopi(GMODE_NUM) if(modeinfolines[i]) cvecprintf(res, "%s %s\n", gmode_enum(1 << i, tmp), escapestring(modeinfolines[i])); + } + else if(!strcmp(mode, "clear")) + { + loopi(GMODE_NUM) if(modeinfolines[i]) { n++; DELSTRING(modeinfolines[i]); } + conoutf("deleted %d modeinfo lines", n); + } + if(modes) loopi(GMODE_NUM) + { + if(modes & (1 << i)) + { + DELSTRING(modeinfolines[i]); + modeinfolines[i] = newstring(line); + } + } + if(res.length()) res.last() = '\0'; + else res.add('\0'); + result(res.getbuf()); + + // recreate HX_MODEINFO + for(int n; (n = findheaderextra(HX_MODEINFO)) >= 0; ) deleteheaderextra(n); + vector p; + entitystats_s es; + calcentitystats(es, NULL, 0); + int todo = es.modes_possible; + bool have_modeinfo = false; + loopk(GMODE_NUM) if(modeinfolines[k] && modeinfolines[k][0] && (todo & (1 << k))) + { + int modes = 0; + for(int i = k; i < GMODE_NUM; i++) + { + if(modeinfolines[i] && !strcmp(modeinfolines[k], modeinfolines[i])) modes |= (1 << i); + } + putuint(p, modes); + sendstring(modeinfolines[k], p); + todo &= ~modes; + have_modeinfo = true; + } + putuint(p, 0); + if(have_modeinfo) headerextras.add(new headerextra(p.length(), HX_MODEINFO|HX_FLAG_PERSIST, p.getbuf())); +} +COMMAND(modeinfo, "ss"); + +void mapartist(char *what) +{ + uchar32 buf; + bool update = false, remove = false;; + + // find and unpack HX_ARTIST + int he = findheaderextra(HX_ARTIST); + if(he >= 0 && headerextras[he]->len == 32) memcpy(buf.u, headerextras[he]->data, 32); + + // parse command arguments and apply changes or generate output + string tmp; + if(!strcmp(what, "print")) conoutf("map artist key: %s", bin2hex(tmp, buf.u, 32)); + else if(!strcmp(what, "set")) + { + if(*sk) + { + memcpy(buf.u, sk + 32, 32); + update = true; + } + else conoutf("no player account in use"); + } + else if(!strcmp(what, "clear")) remove = true; + bin2hex(tmp, buf.u, 32); + result(he >= 0 || update ? tmp : ""); + + // recreate HX_ARTIST + if(update || remove) for(int n; (n = findheaderextra(HX_ARTIST)) >= 0; ) deleteheaderextra(n); + if(update) headerextras.add(new headerextra(32, HX_ARTIST|HX_FLAG_PERSIST, buf.u)); +} +COMMAND(mapartist, "s"); + void setmapinfo(const char *newlicense, const char *newcomment) { string s; @@ -812,10 +919,6 @@ VARF(mapoverride_limitwaveheight, 0, 0, 1, changemapflag(mapoverride_limitwavehe VARF(mapoverride_nostencilshadows, 0, 0, 1, changemapflag(mapoverride_nostencilshadows, MHF_DISABLESTENCILSHADOWS)); VAR(_ignoreillegalpaths, 0, 0, 1); -extern char *mlayout; -extern int Mv, Ma, Hhits; -extern float Mh; - static string lastloadedconfigfile; bool incompatiblemap = false; @@ -972,34 +1075,15 @@ int load_world(char *mname) // still supports all map formats that have e res |= LWW_WORLDERROR * (iabs(we) & 0xf); delete[] smallworld; - - - DELETEA(mlayout); - mlayout = new char[cubicsize + 256]; - memset(mlayout, 0, cubicsize * sizeof(char)); - Mv = Ma = Hhits = 0; - char texuse[256]; - loopi(256) texuse[i] = 0; - loopk(cubicsize) + // check texture slot usage + char texuse[256] = { 0 }; + sqr *s = world; + loopirev(cubicsize) { - sqr *s = &world[k]; - if(SOLID(s)) mlayout[k] = 127; - else - { - mlayout[k] = s->floor; // FIXME - int diff = s->ceil - s->floor; - if(diff > 6) - { - if(diff > MAXMHEIGHT) Hhits += diff - MAXMHEIGHT; - Ma += 1; - Mv += diff; - } - texuse[s->utex] = texuse[s->ftex] = texuse[s->ctex] = 1; - } + if(!SOLID(s)) texuse[s->utex] = texuse[s->ftex] = texuse[s->ctex] = 1; texuse[s->wtex] = 1; + s++; } - Mh = Ma ? (float)Mv/Ma : 0; - c2skeepalive(); calclight(); conoutf("read map %s rev %d (%d milliseconds)", cgzname, hdr.maprevision, watch.elapsed()); diff --git a/source/tools/conv_maxmind-geolite2_to_ac.awk b/source/tools/conv_maxmind-geolite2_to_ac.awk new file mode 100644 index 00000000..035327d9 --- /dev/null +++ b/source/tools/conv_maxmind-geolite2_to_ac.awk @@ -0,0 +1,61 @@ +# convert Geolite2 lists for Assaultcube servers +# +# expects to process GeoLite2-Country-Locations-en.csv and GeoLite2-Country-Blocks-IPv4.csv (in that order) +# +# gawk -f conv_maxmind-geolite2_to_ac.awk GeoLite2-Country-Locations-en.csv GeoLite2-Country-Blocks-IPv4.csv > geoip.cfg +# +# note: the ISO 3166-1 alpha-2 list reserves XA..XZ for user-assigned code elements - we use xs, xp and xz here, see below +# +# download lists from http://dev.maxmind.com/geoip/geoip2/geolite2/ (please don't download too often: once a month should be enough) +# +# thanks to MaxMind: +# This product includes GeoLite2 data created by MaxMind, available from http://www.maxmind.com +# + +BEGIN { +FS="," # expect csv +getcc=0 +} + +# geoname_id,locale_code,continent_code,continent_name,country_iso_code,country_name +$1=="geoname_id" && $5=="country_iso_code" { + getcc=1 # use first line to switch mode + next +} + +# network,geoname_id,registered_country_geoname_id,represented_country_geoname_id,is_anonymous_proxy,is_satellite_provider +$1=="network" && $2=="geoname_id" { + getcc=0 # use first line to switch mode + next +} + +getcc==1 { + if ( length($5)==2 ) + cctab[$1]=$5 # has country_iso_code + else if ( length($3)==2 ) + cctab[$1]=tolower($3) # has continent_code (convert to lowercase to distinguish from country codes) + else + print "ERROR: no usable country code in line " $0 # should not occur + next +} + +getcc==0 { + if ( $2=="" ) { # pick geoname_id over represented_country_geoname_id over registered_country_geoname_id + if ( $4 == "" ) + cc=$3 + else + cc=$4 + } else { + cc=$2 + } + if ( cc=="" && $6!=0 ) + print $1 " xs" # use XS to mark satellite providers with no other country affiliation + else if ( cc=="" && $5!=0 ) + print $1 " xp" # use XP to mark anonymous proxies with no other country affiliation + else if ( cc in cctab ) + print $1 " " cctab[cc] + else + print $1 " xz // " $0 " not found" # use XZ to mark table conversion errors + next +} + diff --git a/source/tools/conv_software77-geoip_to_ac.awk b/source/tools/conv_software77-geoip_to_ac.awk new file mode 100644 index 00000000..5f641047 --- /dev/null +++ b/source/tools/conv_software77-geoip_to_ac.awk @@ -0,0 +1,30 @@ +# convert geo-ip lists from software77 for Assaultcube servers +# +# expects to process IPV4-CSV lists from Webnet77 (please consider donations, if you use their lists) +# +# gawk -f conv_software77-geoip_to_ac.awk IpToCountry.csv > geoip.cfg +# +# download lists from http://software77.net/geo-ip/ (please don't download too often: once a month should be enough) +# +# thanks to Webnet77: http://webnet77.com +# + +BEGIN { +FS="," # expect csv +getcc=0 +} + +function unquote(q) { + gsub("\"", "", q) + return q +} + +substr($1,1,1)=="#" { + next # comment +} + +# expect lines like "38273024","38797311","ripencc","1275523200","KZ","KAZ","Kazakhstan" +{ + print unquote($1) "-" unquote($2) " " unquote($5) " // " unquote($7) + #print unquote($1) "-" unquote($2) " " unquote($5) # use this to drop the comments +}