From f1e879e7fd975683c7bcbf8591e5b3a1a1ceeac3 Mon Sep 17 00:00:00 2001 From: ac_stef Date: Wed, 5 Aug 2009 21:21:57 +0000 Subject: [PATCH] akimbo bug fixed; some new bugs added git-svn-id: svn://svn.code.sf.net/p/actiongame/code/trunk/ac@4598 e016b1c5-ed68-44aa-a2f7-31cac88cffde --- config/docs.cfg | 442 ++++++++++- config/favourites.cfg | 1 + config/font.cfg | 23 +- config/menus.cfg | 2 +- config/menus_multiplayer.cfg | 5 +- config/menus_settings.cfg | 4 +- config/scripts.cfg | 12 +- docs/history.html | 76 +- docs/reference.xml | 453 ++++++++++- packages/maps/official/preview/ac_desert.jpg | Bin 0 -> 4191 bytes packages/maps/preview/readme.txt | 3 + packages/misc/bargraphs.png | Bin 0 -> 622 bytes packages/misc/heart.png | Bin 0 -> 1419 bytes packages/misc/huddigits.png | Bin 3360 -> 3893 bytes packages/misc/serverquality.png | Bin 0 -> 469 bytes source/src/Makefile | 2 +- source/src/client.cpp | 24 +- source/src/clientgame.cpp | 219 ++++-- source/src/clients2c.cpp | 183 +++-- source/src/console.cpp | 27 +- source/src/docs.cpp | 146 ++-- source/src/editing.cpp | 93 ++- source/src/entities.cpp | 31 +- source/src/entity.h | 78 +- source/src/main.cpp | 4 +- source/src/menus.cpp | 101 ++- source/src/physics.cpp | 10 +- source/src/protocol.cpp | 95 ++- source/src/protocol.h | 44 +- source/src/protos.h | 41 +- source/src/renderhud.cpp | 6 +- source/src/rendermodel.cpp | 2 +- source/src/rendertext.cpp | 29 +- source/src/scoreboard.cpp | 150 ++-- source/src/server.cpp | 750 ++++++++++++++----- source/src/server.h | 47 +- source/src/serveractions.h | 33 +- source/src/serverbrowser.cpp | 154 +++- source/src/serverevents.h | 4 +- source/src/serverfiles.h | 45 +- source/src/serverms.cpp | 7 + source/src/tools.cpp | 8 +- source/src/weapon.cpp | 19 +- source/src/wizard.cpp | 32 +- source/src/world.cpp | 122 +-- source/src/worldlight.cpp | 30 +- 46 files changed, 2794 insertions(+), 763 deletions(-) create mode 100644 packages/maps/official/preview/ac_desert.jpg create mode 100644 packages/maps/preview/readme.txt create mode 100644 packages/misc/bargraphs.png create mode 100644 packages/misc/heart.png create mode 100644 packages/misc/serverquality.png diff --git a/config/docs.cfg b/config/docs.cfg index b2ed4570..d08f48e4 100644 --- a/config/docs.cfg +++ b/config/docs.cfg @@ -66,6 +66,12 @@ docargument [B] [the subtrahend] [] [0]; +docident [!] [Performs a negation.]; + +docargument [A] [argument] [] [0]; + + + docident [!=] [Determines if two values are not equal.]; docargument [A] [first value] [] [0]; @@ -208,6 +214,14 @@ docargument [B] [the second value] [] [0]; +docident [*f] [Performs a floating point multiplication.]; + +docargument [A] [the multiplicand] [] [0]; + +docargument [B] [the multiplier] [] [0]; + + + docident [+f] [Adds up two floating-point numbers.]; docargument [A] [the first summand] [float] [0]; @@ -384,6 +398,12 @@ docargument [B] [enable auto map download] [min 0/max 1/default 1]; +docident [autogetnewmaprevisions] [Automatically get new map revisions from the server.]; + +docargument [N] [0: no, 1: yes] [min 0/max 1/default 0]; + + + docident [autoscreenshot] [Toggle for taking an automatic screenshot during intermission.]; docargument [B] [0=Off, 1=On] [min 0/max 1/default 1]; @@ -458,6 +478,12 @@ docremark [Prints details while connecting triangles of 3d models.]; +docident [debugargs] [Dump all command arguments to STDOUT]; + +docargument [...] [...] [] [1]; + + + docident [echo] []; docargument [L] [List of strings] [] [0]; @@ -780,6 +806,10 @@ docargument [B] [the autoreload state] [on (1), off (0) min 0/max 1/default 1]; +docident [benchme] [Move from active team to spectator during match.]; + + + docident [botskill] [changes the skill level for the given bot.]; docargument [N] [botname] [the name of the bot] [0]; @@ -812,6 +842,22 @@ docargument [D] [delta] [how many players to shift +/-] [0]; +docident [clearignored] [Clear list of ignored players.]; + +docargument [A] [optional client number] [] [0]; + +docremark [Omit the client number to clear the whole list.]; + + + +docident [clearmuted] [Clear list of muted players.]; + +docargument [A] [optional client number] [] [0]; + +docremark [Omit the client number to clear the whole list.]; + + + docident [complete] []; docargument [C] [command] [any command or alias] [0]; @@ -874,10 +920,28 @@ docexample [echo playing: (curmap) vote for: (curmap 1)] [output: playing maps/a +docident [curmaprevision] [Current map revision number.]; + +docargument [] [-] [ read-only]; + + + +docident [curmastermode] [Returns the current mastermode]; + + + docident [curmode] [Returns the mode number for the current game.]; +docident [curmodeattr] [Checks the current game mode for certain attributes.]; + +docargument [A] [attribute name] [] [0]; + +docremark [Possible attributes are: team, arena, flag and bot.]; + + + docident [currentprimary] [Returns the weapon-index the local player currently has selected as primary.]; docremark [This is not the same as curweapon - which could be a grenade or the knife.]; @@ -958,6 +1022,10 @@ docremark [This does not work in multiplayer. For entertainment purposes only :) +docident [getclientmode] [Returns the current game mode number.]; + + + docident [getdemo] []; docargument [X] [number in list] [] [0]; @@ -984,6 +1052,14 @@ docexample [idlebots 0] [Will enable the bots to move and shoot.]; +docident [ignore] [Ignore a player.]; + +docargument [A] [client number] [] [0]; + +docremark [You won't see any further game chat or hear any more voice com messages from that player.]; + + + docident [inputcommand] [Makes an input perform a certain command.]; docargument [I] [input] [] [0]; @@ -1020,6 +1096,14 @@ docident [listdemos] []; +docident [listignored] [Print a list of all players that you are currently ignoring.]; + + + +docident [listmuted] [Print a list of all players that you have muted.]; + + + docident [loadcrosshair] [Loads a crosshair for a specific weapon of - if the weapon argument is omitted or <0 - for all weapons.]; docargument [I] [image] [] [0]; @@ -1084,10 +1168,54 @@ docargument [V] [] [min 1/max 32/default 1]; +docident [me] [Action chat message.]; + +docargument [...] [...] [] [1]; + + + docident [minutesremaining] [Returns the remaining minutes of the currently played game. READ ONLY]; +docident [modconnect] [Connect to a modded server.]; + +docargument [A] [IP] [] [0]; + +docargument [B] [port] [] [0]; + +docargument [C] [password] [] [0]; + +docremark [The modified server needs to use the original client-server protocol. The protocol version number will be the negated value of an unmodded server.]; + + + +docident [modconnectadmin] [Connect to a modded server and claim admin.]; + +docargument [A] [IP] [] [0]; + +docargument [B] [port] [] [0]; + +docargument [C] [admin password] [] [0]; + +docremark [The modified server needs to use the original client-server protocol. The protocol version number will be the negated value of an unmodded server.]; + + + +docident [modlanconnect] [Connect to a modified LAN server.]; + +docremark [The modified server needs to use the original client-server protocol. The protocol version number will be the negated value of an unmodded server.]; + + + +docident [muteplayer] [Mute a player]; + +docargument [A] [client number] [] [0]; + +docremark [You won't hear any further voice com messages from that player.]; + + + docident [name] [Sets the nick name for the local player.]; docargument [N] [the name] [] [0]; @@ -1198,7 +1326,19 @@ docremark [See the player model folder for the according skin-id.]; -docident [spectate] [Toggle into spectator mode - only possible when you're dead!]; +docident [skin_cla] [Choose skin when playing for team CLA.]; + +docargument [N] [skin id] [] [0]; + + + +docident [skin_rvsf] [Choose skin when playing for team RVSF.]; + +docargument [N] [skin id] [] [0]; + + + +docident [spectatemode] [Toggle into spectator mode - only possible when you're dead!]; dockey [SPACE] [] [switch spectator mode]; @@ -1298,6 +1438,12 @@ docident [addwp] []; +docident [advancemaprevision] [Select the increment of the map revision number for the next 'savemap'.]; + +docargument [N] [increment] [min 1/max 100/default 1]; + + + docident [applymapsoundchanges] [during map editing, drop all mapsounds so they can be re-added]; @@ -1332,6 +1478,12 @@ docargument [T] [the entity type, see command 'newent'] [string] [0]; +docident [closestenttype] [Restrict 'closest entity' display to one entity type.]; + +docargument [A] [entity type] [] [0]; + + + docident [copy] [Copies the current selection into a buffer.]; docremark [hotkey c]; @@ -1374,6 +1526,12 @@ docident [delwp] []; +docident [editaxis] [Contains the main axis of the player orientation.]; + +docargument [N] [11: X, 12: Y, 13: Z] [min 0/max 13/default 0]; + + + docident [editheight] [Changes the height of the current selection.]; docargument [T] [an integer denoting the type] [0 (floor), 2 (ceiling)] [0]; @@ -1442,6 +1600,10 @@ docremark [Overwrites the closest entity with the specified values.]; +docident [entstats] [Print some map entity statistics to the console.]; + + + docident [equalize] [Levels the floor/ceiling of the selection.]; docargument [T] [an integer denoting the type] [0 (floor), 2 (ceiling)] [0]; @@ -1472,6 +1634,10 @@ docident [getenttype] []; +docident [getmapmsg] [Returns the map message of the current map.]; + + + docident [heightfield] [Marks the current selection as a heightfield.]; docargument [T] [an integer denoting the type] [0 (floor), 2 (ceiling)] [0]; @@ -1490,6 +1656,22 @@ docremark [After changing this value, a "recalc" is needed to see the difference +docident [listmapdependencies] [Prints all media dependencies of a map to a file.]; + +docargument [A] [map name] [] [0]; + +docremark [The output will be added to the file 'mapdependencies.txt'.]; + + + +docident [listmapdependencies_all] [Prints all media dependencies of all maps to a file.]; + +docargument [A] [42] [] [0]; + +docremark [The output will be written to the file 'mapdependencies.txt'.]; + + + docident [Map editing] [A variable indicating if the game is in editmode.]; docargument [B] [editmode] [1 (true), 0 (false) read-only]; @@ -1498,7 +1680,7 @@ docargument [B] [editmode] [1 (true), 0 (false) read-only]; docident [mapenlarge] [Enlarges the current map.]; -docremark [This command will make the current map 1 power of two bigger. So a 6 size map (64x64 units) it will become a 7 map (128x128), with the old map in the middle (from 32-96) and the new areas solid.]; +docremark [This command will make the current map 1 power of two bigger. So a size 6 map (64x64 units) will become a size 7 map (128x128), with the old map in the middle (from 32-96) and the new areas solid.]; @@ -1512,6 +1694,22 @@ docremark [For example: /mapmsg "Map By Author"]; +docident [mapshrink] [Reduces the world size by 1.]; + +docremark [This command will make the current map 1 power of two smaller. So a size 7 map (128x128) will become a 6 size map (64x64 units), by removing 32 cubes from each side. The area to be removed needs to be empty (= all solid).]; + + + +docident [movemap] [Move the whole map (including all entities) in the specified direction.]; + +docargument [dX] [x-offset] [] [0]; + +docargument [dY] [y-offset] [] [0]; + +docargument [dZ] [z-offset] [] [0]; + + + docident [newent] [Adds a new entity]; docargument [type] [the entity type] [light, sound, clip, playerstart, pistol, ammobox, grenades, health, armour, akimbo, mapmodel, ladder, ctf-flag] [0]; @@ -1552,6 +1750,8 @@ docargument [H] [height of the box] [integer] [0]; docremark [Defines a clipping box against which the player will collide.]; +docremark [Use this clip type to clip visible obstacles like fences or the gas tank. If you only want to prevent a player from entering an area, use plclip instead.]; + docident [newent ctf-flag] [Adds a CTF flag entity.]; @@ -1614,6 +1814,24 @@ docremark [The yaw is taken from the current camera yaw.]; +docident [newent plclip] [Adds a player clip entity.]; + +docargument [Z] [elevation above the ground] [integer] [0]; + +docargument [X] [X radius around the box center] [integer] [0]; + +docargument [Y] [Y radius around the box center] [integer] [0]; + +docargument [H] [height of the box] [integer] [0]; + +docremark [Defines a clipping box against which (only) the player will collide.]; + +docremark [Use this clip type to define no-go areas for players without visible obstacles, for example to prevent players from walking on a wall.]; + +docremark [Nades will not be affected by this clip type.]; + + + docident [newent sound] [Adds a sound entity.]; docargument [N] [the sound to play] [integer] [0]; @@ -1638,6 +1856,18 @@ docremark [The new map has 2^S cubes. For S, 6 is small, 7 medium, 8 large.]; +docident [nextclosestent] [Choose another 'closest ent'.]; + +docremark [Use this, when two entities are placed in exactly the same location.]; + + + +docident [nextplayerstart] [Visit next player spawn entity.]; + +docargument [TYPE] [0|1|100] [] [0]; + + + docident [paste] [Pastes a previously copied selection.]; docremark [To paste a selection back requires a same size selection at the destination location. If it is not the same size the selection will be resized automatically prior to the paste operation (with the red dot as anchor), which is easier for large selections.]; @@ -1692,9 +1922,9 @@ docremark [Where you store a map depends on the complexity of what you are creat docident [scalelights] [Scales all lights in the map.]; -docargument [P] [percentage] [] [0]; +docargument [S] [size change (percentage)] [] [0]; -docargument [I] [intensity] [] [0]; +docargument [I] [intensity change (percentage)] [] [0]; docremark [This command is useful if a map is too dark or bright but you want to keep the light entities where they are.]; @@ -1716,6 +1946,44 @@ docremark [Coordinates are as follows: after a "newmap 6" the top-left corner (t +docident [selectionflip] [Flip the selected part of the map at an axis.]; + +docargument [AXIS] [X or Y] [] [0]; + + + +docident [selectionrotate] [Rotate the selected part of the map in 90 degree steps.]; + +docargument [D] [steps] [] [0]; + +docremark [To rotate clockwise, use a positive number of steps. Note, that only quadratic selections can be rotated by 90 degrees.]; + + + +docident [selx] [Current selection: x-coordinate]; + +docargument [N] [x] [min 0/max 4096/default 0]; + + + +docident [selxs] [Current selection: x-span]; + +docargument [N] [xs] [min 0/max 4096/default 0]; + + + +docident [sely] [Current selection: y-coordinate]; + +docargument [N] [y] [min 0/max 4096/default 0]; + + + +docident [selys] [Current selection: y-span]; + +docargument [N] [ys] [min 0/max 4096/default 0]; + + + docident [setjumpwp] []; @@ -1730,6 +1998,12 @@ docremark [Shows what parts of the scenery are rendered using what size cubes, a +docident [showmodelclipping] [Show mapmodel clipping during edit mode.]; + +docargument [N] [-] [min 0/max 1/default 0]; + + + docident [slope] [Makes a slope out of the current selection.]; docargument [X] [x delta step] [integer] [0]; @@ -1878,6 +2152,12 @@ docremark [This command should be placed after newmenu.]; +docident [menuinitselection] [Defines the initial selection for a menu.]; + +docargument [A] [line number] [] [0]; + + + docident [menuitem] [Creates a new menuitem.]; docargument [N] [The menuitem description.] [] [0]; @@ -1912,6 +2192,18 @@ docident [menuitemtextinput] []; +docident [menuitemvar] [Creates a new menuitem with variable content.]; + +docargument [N] [The cubescript to generate the menuitem description.] [] [0]; + +docargument [A] [The command to execute on selection of the menuitem.] [] [0]; + +docargument [H] [The command to execute upon rolling over the menuitem.] [] [0]; + +docremark [Like 'menuitem', but the menuitem description will be the result of an evaluation, everytime the menu gets displayed.]; + + + docident [menumdl] [Specifies a model to render while displaying the last added menu.]; docargument [M] [the model] [] [0]; @@ -1924,6 +2216,26 @@ docargument [S] [the scale] [] [0]; +docident [menuselection] [Selects a line in a menu.]; + +docargument [A] [menu name] [] [0]; + +docargument [B] [line number] [] [0]; + + + +docident [menuselectionbgcolor] [Defines the background color for the menu selection bar.]; + +docargument [R] [red (0..100)] [] [0]; + +docargument [G] [green (0..100)] [] [0]; + +docargument [B] [blue (0..100)] [] [0]; + +docargument [A] [alpha (0..100)] [] [0]; + + + docident [newmenu] [Creates a new menu.]; docargument [N] [The name of the menu] [] [0]; @@ -2034,6 +2346,12 @@ docargument [V] [] [min 0/max 100/default 40]; +docident [hidebigmenuimages] [Hide big images in menus.]; + +docargument [N] [0: show, 1: hide] [min 0/max 1/default 0]; + + + docident [hidecompass] []; docargument [V] [] [min 0/max 1/default 0]; @@ -2138,6 +2456,12 @@ docargument [V] [] [min 0/max 1/default 0]; +docident [oldfashionedgunstats] [Show ammo stats like in 1.0]; + +docargument [N] [0: new, 1: old] [min 0/max 1/default 0]; + + + docident [radarentsize] []; docargument [V] [] [min 1/max 64/default 4]; @@ -2174,6 +2498,12 @@ docargument [V] [] [min 0/max 1/default 0]; +docident [righthanded] [Choose player hand to carry the weapon.]; + +docargument [N] [0: lefty, 1: righty] [min 0/max 1/default 1]; + + + docident [swaymovediv] []; docargument [V] [] [min 1/max 1000/default 200]; @@ -2724,6 +3054,16 @@ docargument [V] [] [min 0/max 100/default 40]; +docident [music] [Play music in the background.]; + +docargument [A] [music file name] [] [0]; + +docargument [B] [playtime] [] [0]; + +docargument [C] [command to be executed, when music is done] [] [0]; + + + docident [musicvol] [Sets the music volume.]; docargument [N] [the volume] [min 0/max 255/default 128]; @@ -2914,6 +3254,14 @@ docsection [Server commands] +docident [addfavcategory] [Adds a new category in the serverbrowser favourites.]; + +docargument [A] [reference designator (keep short and unique)] [] [0]; + +docremark [Add new categories to your autoexec.cfg, check favourites.cfg for examples.]; + + + docident [autoteam] [Sets automated team assignment.]; docargument [B] [Enables or disables auto team.] [1 (On), 0 (Off)] [0]; @@ -2940,6 +3288,12 @@ docremark [This command is wrapped by aliases for better usability and is used t +docident [deleteservermap] [Deletes a map from the current server.]; + +docargument [A] [map name] [] [0]; + + + docident [forceteam] [Forces the specified player to join the enemy team.]; docargument [CN] [The player to assign to a team] [Client number] [0]; @@ -2954,15 +3308,25 @@ docremark [Requires admin state. The admin will lose his admin state after succe +docident [hidefavicons] [Hide favourites icons in serverbrowser.]; + +docargument [N] [0: show, 1: hide] [min 0/max 1/default 0]; + + + docident [kick] [Kicks the specified player from the server.]; docargument [CN] [The player to kick] [Client number] [0]; +docident [listfavcats] [List all registered serverbrowser favourites categories]; + + + docident [mastermode] [Sets the mastermode for the server.]; -docargument [N] [The master mode] [0 (Open), 1 (Private)] [0]; +docargument [N] [The master mode] [0 (Open), 1 (Private), 2 (Match)] [0]; docremark [If the mastermode is set to 'private', no more clients can join the server. Default is 'open' which allows anyone to join the server.]; @@ -2990,11 +3354,31 @@ docargument [V] [] [min 0/max 2/default 1]; -docident [sendmap] [Sends the current map to the server and sends other players a message about it.]; +docident [searchnickname] [Search a nickname (or -part) on all servers.]; -docargument [M] [Map to save and reload locally] [] [0]; +docargument [N] [nickname to search] [] [0]; -docremark [If the map argument is specified, the map is first saved and then reloaded before sending it. Note: It's always required to be on the map one wants to send before issuing this command!]; + + +docident [sendmap] [Sends a map to the server.]; + +docargument [M] [map to send] [] [0]; + +docremark [During coop edit, the current map gets saved to file and sent to the server. Other players can use 'getmap' to download it.]; + +docremark [When not in edit mode, the map will not be saved. The new map will be used, when the next game on that map starts on the server.]; + + + +docident [serverbrowserhidefavtag] [Hide favourites tag column in serverbrowser]; + +docargument [N] [-] [min 0/max 2/default 1]; + + + +docident [serverbrowserhideip] [Hide server IP and port in serverbrowser.]; + +docargument [N] [-] [min 0/max 2/default 2]; @@ -3020,6 +3404,16 @@ docargument [V] [] [min 0/max NUMSERVSORT-1/default 0]; +docident [serversortdir] [Select ascending of descending sort order in serverbrowser.]; + + + +docident [serversortpreferofficial] [Sort official maps over custom maps in serverbrowser.]; + +docargument [N] [-] [min 0/max 1/default 1]; + + + docident [servpingrate] []; docargument [V] [] [min 1000/max 60000/default 5000]; @@ -3042,6 +3436,38 @@ docargument [V] [] [min 0/max 1/default 1]; +docident [showminremain] [Show 'minutes remaining' in serverbrowser.]; + +docargument [N] [] [min 0/max 1/default 0]; + + + +docident [shownamesinbrowser] [Show player names in serverbrowser.]; + +docargument [N] [-] [min 0/max 1/default 0]; + + + +docident [showonlyfavourites] [Show only servers of one favourites category in serverbrowser.]; + +docargument [N] [category index] [min 0/max 100/default 0]; + + + +docident [showonlygoodservers] [Show only servers with the correct protocol in serverbrowser.]; + +docargument [N] [-] [min 0/max 1/default 0]; + + + +docident [showweights] [Show 'weights' in serverbrowser.]; + +docargument [N] [] [min 0/max 1/default 0]; + +docremark ['weights' are the sort criteria with the highest priority. Favourites categories can change the weights. Use 'showweights' to debug problems with serverbrowser sorting.]; + + + docsection [Game modes] diff --git a/config/favourites.cfg b/config/favourites.cfg index 6ce61f5e..1fc385a2 100644 --- a/config/favourites.cfg +++ b/config/favourites.cfg @@ -54,6 +54,7 @@ sbfavourite_LOVE_red = 0 sbfavourite_LOVE_green = 100 sbfavourite_LOVE_blue = 0 sbfavourite_LOVE_weight = 255 // because love trumps hate, sheldon +sbfavourite_LOVE_image = packages/misc/heart.png addfavcategory LIKE sbfavourite_LIKE_desc = "Nice, too" diff --git a/config/font.cfg b/config/font.cfg index 24427b7d..801323c3 100644 --- a/config/font.cfg +++ b/config/font.cfg @@ -95,7 +95,8 @@ fontchar 270 448 40 64 //} fontchar 310 448 53 64 //~ font huddigits "packages/misc/huddigits.png" 40 64 -fontskip 48 +fontskip 47 +fontchar 476 64 24 64 /// fontchar 4 64 40 64 //0 fontchar 52 64 40 64 //1 fontchar 100 64 40 64 //2 @@ -107,3 +108,23 @@ fontchar 340 64 40 64 //7 fontchar 388 64 40 64 //8 fontchar 436 64 40 64 //9 +font serverquality "packages/misc/serverquality.png" 48 64 +fontskip 48 +fontchar 0 64 48 64 //0 "unknown" +fontchar 48 64 48 64 //1 "very bad" +fontchar 96 64 48 64 //2 "bad" +fontchar 144 64 48 64 //3 "medium" +fontchar 192 64 48 64 //4 "good" +fontchar 240 64 48 64 //5 "very good" + +font bargraphs "packages/misc/bargraphs.png" 32 64 +fontskip 48 +fontchar 0 64 32 64 //0 [ ][ ] +fontchar 32 64 32 64 //1 [x][ ] +fontchar 64 64 32 64 //2 [ ][x] +fontchar 96 64 32 64 //3 [x][x] +fontchar 128 64 64 64 //4 [black] +fontchar 192 64 64 64 //5 [green] +fontchar 256 64 64 64 //6 [yellow] +fontchar 320 64 64 64 //7 [red] + diff --git a/config/menus.cfg b/config/menus.cfg index c67b8258..0476c9f4 100755 --- a/config/menus.cfg +++ b/config/menus.cfg @@ -22,7 +22,7 @@ alias genmapitems [ alias n (listlen $arg1) loop i $n [ alias listmapname (at $arg1 $i) - menuitem $listmapname (concat map $listmapname) + menuitemimage (concatword "packages/maps/official/preview/" $listmapname ".jpg") $listmapname (concat map $listmapname) alias next (+ $i 1) if (= $next $n) [ alias next 0 ] [] alias nextmap (at $arg1 $next) diff --git a/config/menus_multiplayer.cfg b/config/menus_multiplayer.cfg index 19f8296d..6af66d58 100644 --- a/config/menus_multiplayer.cfg +++ b/config/menus_multiplayer.cfg @@ -32,8 +32,9 @@ menuitem " 3Quit AssaultCube" "savequit" menuinitselection 12 alias __ingamemenu_showmodel [ - if (curteam) [alias __curskin (+ 1 $skin_rvsf)] [alias __curskin (+ 1 $skin_cla)] - if $arg2 [chmenumdl $arg1 (concatword "playermodels/" (at "CLA RVSF" (curteam)) "/0" $__curskin) idle 15 3] [chmenumdl $arg1] + if (< (curteam) 4) [ + if $arg2 [chmenumdl $arg1 (concatword "playermodels/" (at "CLA RVSF" (curteam)) "/0" (+ 1 (skin))) idle 15 3] [chmenumdl $arg1] + ] ] // In-game menu that opens if when you press escape if you're admin state and connected to a game. diff --git a/config/menus_settings.cfg b/config/menus_settings.cfg index 63072679..873b6d6b 100644 --- a/config/menus_settings.cfg +++ b/config/menus_settings.cfg @@ -19,7 +19,7 @@ menuitem "V-19 Combat Shotgun" "nextprimary 2;echo V-19 Combat Shotgun selecte newmenu CLA -menuinit [menuselection CLA $skin_cla] +menuinit [menuselection CLA (skin_cla)] menumdl playermodels/CLA/01 all 50 4 menuitem "Comandante" "skin_cla 0" "chmenumdl CLA playermodels/CLA/01 all 50 4" menuitem "Psycho" "skin_cla 1" "chmenumdl CLA playermodels/CLA/02 all 50 4" @@ -29,7 +29,7 @@ menuitem "Ripper" "skin_cla 3" "chmenumdl CLA playermodels/CLA/04 newmenu RVSF -menuinit [menuselection RVSF $skin_rvsf] +menuinit [menuselection RVSF (skin_rvsf)] menumdl playermodels/RVSF/01 all 50 4 menuitem "Alpha" "skin_rvsf 0" "chmenumdl RVSF playermodels/RVSF/01 all 50 4" menuitem "Tango" "skin_rvsf 1" "chmenumdl RVSF playermodels/RVSF/02 all 50 4" diff --git a/config/scripts.cfg b/config/scripts.cfg index 8e870fe9..f97e6efd 100644 --- a/config/scripts.cfg +++ b/config/scripts.cfg @@ -30,11 +30,13 @@ loop i (listlen $mapcommands) [ complete demo "demos" dmo -alias entcomplete [listcomplete $arg1 "light playerstart pistol ammobox grenades health armour akimbo mapmodel ladder ctf-flag sound clip"] +alias entcomplete [listcomplete $arg1 "light playerstart pistol ammobox grenades health armour akimbo mapmodel ladder ctf-flag sound clip plclip"] entcomplete newent entcomplete clearents entcomplete closestenttype +listcomplete team "CLA RVSF CLA-SPECT RVSF-SPECT SPECTATOR" + // Game mode descriptions... gamemodedesc 0 [Team Deathmatch: Find the enemy team and destroy them!] gamemodedesc 1 [Co-operative edit: Use this mode to edit maps with others online!] @@ -57,13 +59,7 @@ gamemodedesc 15 [Keep the Flag: Get the flag and keep it. You score one point ev alias resetbinds [ exec config/resetbinds.cfg; echo " 3All binds have been reset to default values!" ] // "changeteam" command. -alias changeteam [ - if (= (curteam) 1) [ team CLA; alias tmp_oldteam 1 ] [ team RVSF; alias tmp_oldteam 0 ] - if (curmodeattr team) [ - if (= (curteam) 0) [ if (= $tmp_oldteam 0) [ echo "You stayed on team 3CLA" ] [ echo "You switched to team 3CLA" ] ] - if (= (curteam) 1) [ if (= $tmp_oldteam 1) [ echo "You stayed on team 1RVSF" ] [ echo "You switched to team 1RVSF" ] ] - ] -] +alias changeteam [ if (< (curteam) 2) [ team (at "RVSF CLA" (curteam)) ] ] // Mode/map-setting aliases. diff --git a/docs/history.html b/docs/history.html index 1215b78a..1415f010 100644 --- a/docs/history.html +++ b/docs/history.html @@ -18,6 +18,80 @@

History

+ + + + + + +

+ AssaultCube trunk development (to be removed later - it is just too much for a commit comment)

+ + added with 'akimbo bug fixed' commit: + +

gameplay:

+ + +

server:

+ + +

client:

+ + +

editing:

+ + +

code:

+ + +

doc:

+ + + + + + +

AssaultCube v1.0.4

@@ -469,7 +543,7 @@

The day makkE came to town...

- **** BEGIN LOGGING AT Sun Aug 1 11:46:13 2004
+
  • BEGIN LOGGING AT Sun Aug 1 11:46:13 2004
    Aug 01 16:12:02 --> makkE (makkE@116-1-87.w193-250.abo.wanadoo.fr) has joined #ActionCube
    Aug 01 16:12:18 <makkE> hi
    diff --git a/docs/reference.xml b/docs/reference.xml index 404169f8..0b5a5302 100644 --- a/docs/reference.xml +++ b/docs/reference.xml @@ -277,6 +277,20 @@ + + Performs a negation. + + + + + + Performs a floating point multiplication. + + + + + + Random value @@ -476,6 +490,13 @@ + + Dump all command arguments to STDOUT + + + + + @@ -755,6 +776,10 @@ Determines if the current played map should be automatically downloaded if it is not available locally. + + Automatically get new map revisions from the server. + + Sets the correction value for clockfix. @@ -1110,7 +1135,7 @@ - + changes the skill level for all bots. @@ -1123,7 +1148,7 @@ - + Enables or disables the ability of the bots to fire their weapons @@ -1186,6 +1211,29 @@ + + Current map revision number. + + + + Checks the current game mode for certain attributes. + + + + + + Possible attributes are: team, arena, flag and bot. + + + + + Returns the current game mode number. + + + + Returns the current mastermode + + Returns the weapon-index the local player currently has selected as primary. @@ -1344,6 +1392,18 @@ bind PRINT [ + + Choose skin when playing for team CLA. + + + + + + Choose skin when playing for team RVSF. + + + + Sets the nick name for the local player. @@ -1362,6 +1422,62 @@ bind PRINT [ + + Ignore a player. + + + + + + You won't see any further game chat or hear any more voice com messages from that player. + + + + + Print a list of all players that you are currently ignoring. + + + Clear list of ignored players. + + + + + + Omit the client number to clear the whole list. + + + + + Mute a player + + + + + + You won't hear any further voice com messages from that player. + + + + + Clear list of muted players. + + + + + + Omit the client number to clear the whole list. + + + + + Print a list of all players that you have muted. + + + Action chat message. + + + + Puts a prompt on screen. @@ -1525,6 +1641,46 @@ bind PRINT [ + + Move from active team to spectator during match. + + + Connect to a modded server. + + + + + + + + The modified server needs to use the original client-server protocol. The protocol version number + will be the negated value of an unmodded server. + + + + + Connect to a modded server and claim admin. + + + + + + + + The modified server needs to use the original client-server protocol. The protocol version number + will be the negated value of an unmodded server. + + + + + Connect to a modified LAN server. + + + The modified server needs to use the original client-server protocol. The protocol version number + will be the negated value of an unmodded server. + + + Shows or hides the scores. @@ -1596,7 +1752,7 @@ Output: - + Toggle into spectator mode - only possible when you're dead! @@ -1768,7 +1924,7 @@ Output: This is a debugging command and only works for single player modes. - + When used you will see what the bot sees. Type it again (with or without name) to return to the game(you will respawn). @@ -2109,6 +2265,29 @@ Output: Defines a clipping box against which the player will collide. + + Use this clip type to clip visible obstacles like fences or the gas tank. If you only want to prevent a player from entering an area, use plclip instead. + + + + + + + + + + + Adds a player clip entity. + + + Defines a clipping box against which (only) the player will collide. + + + Use this clip type to define no-go areas for players without visible obstacles, for example to prevent players from walking on a wall. + + + Nades will not be affected by this clip type. + @@ -2197,7 +2376,7 @@ Output: This command will make the current map 1 power of two bigger. - So a 6 size map (64x64 units) it will become a 7 map (128x128), + So a size 6 map (64x64 units) will become a size 7 map (128x128), with the old map in the middle (from 32-96) and the new areas solid. @@ -2205,6 +2384,16 @@ Output: + + Reduces the world size by 1. + + + This command will make the current map 1 power of two smaller. + So a size 7 map (128x128) will become a 6 size map (64x64 units), by removing 32 cubes from each side. + The area to be removed needs to be empty (= all solid). + + + Sets the map message, which will be displayed when the map loads. @@ -2215,6 +2404,107 @@ Output: + + Returns the map message of the current map. + + + Prints all media dependencies of a map to a file. + + + + + + The output will be added to the file 'mapdependencies.txt'. + + + + + Prints all media dependencies of all maps to a file. + + + + + + The output will be written to the file 'mapdependencies.txt'. + + + + + Print some map entity statistics to the console. + + + Restrict 'closest entity' display to one entity type. + + + + + + Choose another 'closest ent'. + + + Use this, when two entities are placed in exactly the same location. + + + + + Visit next player spawn entity. + + + + + + Move the whole map (including all entities) in the specified direction. + + + + + + + + Flip the selected part of the map at an axis. + + + + + + Rotate the selected part of the map in 90 degree steps. + + + + + + To rotate clockwise, use a positive number of steps. Note, that only quadratic selections can be rotated by 90 degrees. + + + + + Contains the main axis of the player orientation. + + + + Select the increment of the map revision number for the next 'savemap'. + + + + Current selection: x-coordinate + + + + Current selection: x-span + + + + Current selection: y-coordinate + + + + Current selection: y-span + + + + Show mapmodel clipping during edit mode. + + Sets the global water level for the map. @@ -2370,8 +2660,8 @@ Output: - - + + @@ -2450,6 +2740,45 @@ Output: + + Defines the initial selection for a menu. + + + + + + Creates a new menuitem with variable content. + + + Like 'menuitem', but the menuitem description will be the result of an evaluation, everytime the menu gets displayed. + + + + + + + + + + + + + + Selects a line in a menu. + + + + + + + Defines the background color for the menu selection bar. + + + + + + + @@ -2458,7 +2787,7 @@ Output: - + @@ -2585,6 +2914,18 @@ Output: + + Hide big images in menus. + + + + Show ammo stats like in 1.0 + + + + Choose player hand to carry the weapon. + + @@ -3129,6 +3470,14 @@ Output: + + Play music in the background. + + + + + + Enables or disables the audio subsystem in AC. @@ -3405,6 +3754,72 @@ Output: + + Adds a new category in the serverbrowser favourites. + + + Add new categories to your autoexec.cfg, check favourites.cfg for examples. + + + + + + + + List all registered serverbrowser favourites categories + + + Hide favourites icons in serverbrowser. + + + + Hide favourites tag column in serverbrowser + + + + Hide server IP and port in serverbrowser. + + + + Select ascending of descending sort order in serverbrowser. + + + Sort official maps over custom maps in serverbrowser. + + + + Show 'minutes remaining' in serverbrowser. + + + + Show player names in serverbrowser. + + + + Show only servers of one favourites category in serverbrowser. + + + + Show only servers with the correct protocol in serverbrowser. + + + + Show 'weights' in serverbrowser. + + + + 'weights' are the sort criteria with the highest priority. Favourites categories can change the weights. + Use 'showweights' to debug problems with serverbrowser sorting. + + + + + Search a nickname (or -part) on all servers. + + + + + Sets automated team assignment. @@ -3505,7 +3920,7 @@ Output: - + @@ -3519,20 +3934,30 @@ Output: - Sends the current map to the server and sends other players a message about it. + Sends a map to the server. - If the map argument is specified, the map is first saved and then reloaded before sending it. - Note: It's always required to be on the map one wants - to send before issuing this command! + During coop edit, the current map gets saved to file and sent to the server. + Other players can use 'getmap' to download it. + + + When not in edit mode, the map will not be saved. The new map will be used, when + the next game on that map starts on the server. - + + + + + + Deletes a map from the current server. + + diff --git a/packages/maps/official/preview/ac_desert.jpg b/packages/maps/official/preview/ac_desert.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ebc5ee2021547118b7491ed2785e3123c4531ff2 GIT binary patch literal 4191 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 literal 0 HcmV?d00001 diff --git a/packages/misc/heart.png b/packages/misc/heart.png new file mode 100644 index 0000000000000000000000000000000000000000..72846f2069e0ad634cbf91f77793a6094d3fda0c GIT binary patch literal 1419 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 literal 3360 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 A&;S4c literal 0 HcmV?d00001 diff --git a/source/src/Makefile b/source/src/Makefile index 3d46f2d7..298bde64 100644 --- a/source/src/Makefile +++ b/source/src/Makefile @@ -2,7 +2,7 @@ CXXOPTFLAGS=-O3 -fomit-frame-pointer INCLUDES1=-I/usr/X11R6/include -I../enet/include -I../src INCLUDES2=`sdl-config --cflags` #CXXCUSTOMFLAGS=-fsigned-char -Wall -Wextra -Wno-missing-field-initializers -Wno-unused -Wpointer-arith -Wconversion -rdynamic -CXXCUSTOMFLAGS=-fsigned-char -Wall -Wconversion -rdynamic +CXXCUSTOMFLAGS=-fsigned-char -Wall -rdynamic CXXFLAGS=$(CXXOPTFLAGS) $(INCLUDES1) $(INCLUDES2) $(CXXCUSTOMFLAGS) CXXFLAGSSERVER=$(CXXOPTFLAGS) $(INCLUDES1) $(CXXCUSTOMFLAGS) diff --git a/source/src/client.cpp b/source/src/client.cpp index 5405314d..61e3d9ce 100644 --- a/source/src/client.cpp +++ b/source/src/client.cpp @@ -215,13 +215,24 @@ void trydisconnect() disconnect(0, !discmillis); } -void toserver(char *text) +void _toserver(char *text, int msg, int msgt) { - bool toteam = text && text[0] == '%' && m_teammode; + bool toteam = text && text[0] == '%' && (m_teammode || team_isspect(player1->team)); if(!toteam && text[0] == '%' && strlen(text) > 1) text++; // convert team-text to normal-text if no team-mode is active if(toteam) text++; - conoutf("%s:\f%d %s", colorname(player1), toteam ? 1 : 0, text); - addmsg(toteam ? SV_TEAMTEXT : SV_TEXT, "rs", 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); +} + +void toserver(char *text) +{ + _toserver(text, SV_TEXT, SV_TEAMTEXT); +} + +void toserverme(char *text) +{ + _toserver(text, SV_TEXTME, SV_TEAMTEXTME); } void echo(char *text) @@ -237,6 +248,7 @@ void echo(char *text) COMMAND(echo, ARG_CONC); COMMANDN(say, toserver, ARG_CONC); +COMMANDN(me, toserverme, ARG_CONC); COMMANDN(connect, connectserv, ARG_3STR); COMMAND(connectadmin, ARG_3STR); COMMAND(lanconnect, ARG_NONE); @@ -358,8 +370,8 @@ void c2sinfo(playerent *d) // send update to the server c2sinit = true; putint(p, SV_INITC2S); sendstring(player1->name, p); - putint(p, player1->team); - putint(p, player1->skin); + putint(p, player1->skin(TEAM_CLA)); + putint(p, player1->skin(TEAM_RVSF)); } if(sendmapidenttoserver) { // new map diff --git a/source/src/clientgame.cpp b/source/src/clientgame.cpp index 889d7a8a..e9dcaef5 100644 --- a/source/src/clientgame.cpp +++ b/source/src/clientgame.cpp @@ -18,18 +18,17 @@ void mode(int n) COMMAND(mode, ARG_1INT); bool intermission = false; -bool autoteambalance = false; int arenaintermission = 0; +struct serverstate servstate = { 0 }; playerent *player1 = newplayerent(); // our client -vector players; // other clients +vector players; // other clients int lastmillis = 0, totalmillis = 0; int lasthit = 0; int curtime = 0; string clientmap; - -extern int framesinmap; +int spawnpermission = SP_WRONGMAP; char *getclientmap() { return clientmap; } @@ -37,12 +36,11 @@ int getclientmode() { return gamemode; } extern bool c2sinit, sendmapidenttoserver; -void setskin(playerent *pl, uint skin) +void setskin(playerent *pl, int skin, int team) { if(!pl) return; - if(pl == player1) c2sinit=false; - const int maxskin[2] = { 4, 6 }; - pl->skin = skin % maxskin[team_base(pl->team)]; + if(pl == player1) c2sinit = false; + pl->setskin(team, skin); } bool duplicatename(playerent *d, char *name = NULL) @@ -81,12 +79,90 @@ char *colorpj(int pj) return cpj; } +const char *highlight(const char *text) +{ + static char result[MAXTRANS + 10]; + const char *marker = getalias("HIGHLIGHT"), *sep = " ,;:!\"'"; + if(!marker || !strstr(text, player1->name)) return text; + filterrichtext(result, marker); + s_sprintfd(subst)("\fs%s%s\fr", result, player1->name); + char *temp = newstring(text); + char *s = strtok(temp, sep), *l = temp, *c, *r = result; + result[0] = '\0'; + while(s) + { + if(!strcmp(s, player1->name)) + { + if(MAXTRANS - strlen(result) > strlen(subst) + (s - l)) + { + for(c = l; c < s; c++) *r++ = text[c - temp]; + *r = '\0'; + strcat(r, subst); + } + l = s + strlen(s); + } + s = strtok(NULL, sep); + } + if(MAXTRANS - strlen(result) > strlen(text) - (l - temp)) strcat(result, text + (l - temp)); + delete[] temp; + return *result ? result : text; +} + +void ignore(int cn) +{ + playerent *d = getclient(cn); + if(d) d->ignored = true; +} + +void listignored() +{ + string pl; + pl[0] = '\0'; + loopv(players) if(players[i] && players[i]->ignored) s_strcatf(pl, ", %s", colorname(players[i])); + if(*pl) conoutf("ignored players: %s", pl + 2); + else conoutf("no players ignored."); +} + +void clearignored(char *ccn) +{ + int cn = ccn && *ccn ? atoi(ccn) : -1; + loopv(players) if(players[i] && (cn < 0 || cn == i)) players[i]->ignored = false; +} + +void muteplayer(int cn) +{ + playerent *d = getclient(cn); + if(d) d->muted = true; +} + +void listmuted() +{ + string pl; + pl[0] = '\0'; + loopv(players) if(players[i] && players[i]->muted) s_strcatf(pl, ", %s", colorname(players[i])); + if(*pl) conoutf("muted players: %s", pl + 2); + else conoutf("no players muted."); +} + +void clearmuted(char *ccn) +{ + int cn = ccn && *ccn ? atoi(ccn) : -1; + loopv(players) if(players[i] && (cn < 0 || cn == i)) players[i]->muted = false; +} + +COMMAND(ignore, ARG_1INT); +COMMAND(listignored, ARG_NONE); +COMMAND(clearignored, ARG_1STR); +COMMAND(muteplayer, ARG_1INT); +COMMAND(listmuted, ARG_NONE); +COMMAND(clearmuted, ARG_1STR); + void newname(const char *name) { if(name[0]) { c2sinit = false; - filtertext(player1->name, name, 1, MAXNAMELEN); + filtertext(player1->name, name, 0, MAXNAMELEN); if(!player1->name[0]) s_strcpy(player1->name, "unarmed"); updateclientname(player1); } @@ -94,54 +170,46 @@ void newname(const char *name) alias("curname", player1->name); } -int smallerteam() +int teamatoi(const char *name) { - int teamsize[2] = {0, 0}; - loopv(players) if(players[i] && !team_isspect(players[i]->team)) teamsize[players[i]->team]++; - if(!team_isspect(player1->team)) teamsize[player1->team]++; - if(teamsize[0] == teamsize[1]) return -1; - return teamsize[0] < teamsize[1] ? 0 : 1; -} - -void changeteam(int team, bool respawn) // force team and respawn -{ - c2sinit = false; - if(m_flags) tryflagdrop(false); - player1->team = team; - if(respawn) addmsg(SV_CHANGETEAM, "r"); + string uc; + strtoupper(uc, name); + loopi(TEAM_NUM) if(!strcmp(teamnames[i], uc)) return i; + return -1; } void newteam(char *name) { - if(name[0]) + if(*name) { - // if(m_teammode) - { - int nt = -1; - loopi(TEAM_NUM) if(!strcmp(teamnames[i], name)) nt = i; - if(nt == player1->team) return; // same team - if(player1->isspectating() && !(player1->state==CS_DEAD && player1->spectatemode == SM_DEATHCAM)) { conoutf("\f3you can not switch teams when spectating"); return; } - if(nt < 0 || nt > 1) { conoutf("\f3\"%s\" is not a valid team name (try CLA or RVSF)", name); return; } // FIXME - - bool checkteamsize = m_teammode && autoteambalance && players.length() >= 1 && !m_botmode; - int freeteam = smallerteam(); - - if(checkteamsize && nt != freeteam) - { - conoutf("\f3the %s team is already full", teamnames[nt]); - return; - } - changeteam(nt); - } + int nt = teamatoi(name); + if(nt == player1->team) return; // same team + if(!team_isvalid(nt)) { conoutf("\f3\"%s\" is not a valid team name (try CLA, RVSF or SPECTATOR)", name); return; } + addmsg(SV_TRYTEAM, "ri", nt); + if(m_flags) tryflagdrop(false); } else conoutf("your team is: %s", team_string(player1->team)); } -VARNP(skin_cla, nextskin_cla, 0, 0, 1000); -VARNP(skin_rvsf, nextskin_rvsf, 0, 0, 1000); +void benchme() +{ + if(team_isactive(player1->team) && servstate.mastermode == MM_MATCH) + addmsg(SV_TRYTEAM, "ri", team_tospec(player1->team)); +} + +int _setskin(char *s, int t) +{ + if(s && *s) setskin(player1, atoi(s), t); + return player1->skin(t); +} + +ICOMMANDF(skin_cla, ARG_1EST, (char *s) { return _setskin(s, TEAM_CLA); }); +ICOMMANDF(skin_rvsf, ARG_1EST, (char *s) { return _setskin(s, TEAM_RVSF); }); +ICOMMANDF(skin, ARG_1EST, (char *s) { return _setskin(s, player1->team); }); int curteam() { return player1->team; } int currole() { return player1->clientrole; } +int curmastermode() { return servstate.mastermode; } void curmap(int cleaned) { result(cleaned ? behindpath(getclientmap()) : getclientmap()); } int curmodeattr(char *attr) @@ -155,27 +223,16 @@ int curmodeattr(char *attr) COMMANDN(team, newteam, ARG_1STR); COMMANDN(name, newname, ARG_1STR); +COMMAND(benchme, ARG_NONE); COMMAND(curteam, ARG_IVAL); COMMAND(currole, ARG_IVAL); +COMMAND(curmastermode, ARG_IVAL); COMMAND(getclientmode, ARG_IVAL); COMMAND(curmodeattr, ARG_1EST); COMMAND(curmap, ARG_1INT); VARP(showscoresondeath, 0, 1, 1); VARP(autoscreenshot, 0, 0, 1); -void nextskin_player1() -{ - switch(player1->team) - { - case TEAM_CLA: - if(player1->skin != nextskin_cla) setskin(player1, nextskin_cla); - break; - case TEAM_RVSF: - if(player1->skin != nextskin_rvsf) setskin(player1, nextskin_rvsf); - break; - } -} - void deathstate(playerent *pl) { pl->state = CS_DEAD; @@ -192,6 +249,13 @@ void deathstate(playerent *pl) setscope(false); if(editmode) toggleedit(true); damageblend(-1); + if(pl->team == TEAM_SPECT) spectate(SM_FLY); + else if(team_isspect(pl->team)) spectate(SM_FOLLOW1ST); + if(pl->spectatemode == SM_DEATHCAM) + { + player1->followplayercn = FPCN_DEATHCAM; + addmsg(SV_SPECTCN, "ri", player1->followplayercn); + } } else pl->resetinterp(); } @@ -202,7 +266,6 @@ void spawnstate(playerent *d) // reset player state not persistent d->spawnstate(gamemode); if(d==player1) { - nextskin_player1(); setscope(false); } } @@ -348,7 +411,7 @@ int lastspawnattempt = 0; void showrespawntimer() { - if(intermission) return; + if(intermission || spawnpermission > SP_OK_NUM) return; if(m_arena) { if(!arenaintermission) return; @@ -510,7 +573,11 @@ void respawnself() bool tryrespawn() { - if(player1->state==CS_DEAD) + if(spawnpermission > SP_OK_NUM) + { + hudeditf(HUDMSG_TIMER, "\f3%s", spawnpermission == SP_WRONGMAP ? "You have to be on the correct map to spawn. Type /getmap" : "You can't spawn. You're a spectator."); + } + else if(player1->state==CS_DEAD) { int respawnmillis = player1->respawnoffset+(m_arena ? 0 : (m_flags ? 5000 : 2000)); if(lastmillis>respawnmillis) @@ -656,7 +723,7 @@ void initclient() { clientmap[0] = 0; newname("unarmed"); - changeteam(rnd(2), false); + player1->team = TEAM_SPECT; } entity flagdummies[2] = // in case the map does not provide flags @@ -717,19 +784,22 @@ void gamemodedesc(char *modenr, char *desc) COMMAND(gamemodedesc, ARG_2STR); -void resetmap() +void resetmap(bool mrproper) { resetsleep(); clearminimap(); cleardynlights(); pruneundos(); - audiomgr.clearworldsounds(); particlereset(); - setvar("gamespeed", 100); - setvar("paused", 0); - setvar("fog", 180); - setvar("fogcolour", 0x8099B3); - setvar("shadowyaw", 45); + if(mrproper) + { + audiomgr.clearworldsounds(); + setvar("gamespeed", 100); + setvar("paused", 0); + setvar("fog", 180); + setvar("fogcolour", 0x8099B3); + setvar("shadowyaw", 45); + } } int suicided = -1; @@ -1102,7 +1172,7 @@ playerent *updatefollowplayer(int shiftdirection) vector available; loopv(players) if(players[i]) { - if(m_teammode && !watchingdemo && !isteam(players[i]->team, player1->team)) continue; + if(player1->team != TEAM_SPECT && !watchingdemo && m_teammode && team_base(players[i]->team) != team_base(player1->team)) continue; if(players[i]->state==CS_DEAD || players[i]->isspectating()) continue; available.add(players[i]); } @@ -1115,6 +1185,7 @@ playerent *updatefollowplayer(int shiftdirection) int idx = (oldidx+shiftdirection) % available.length(); if(idx<0) idx += available.length(); + if(player1->followplayercn != available[idx]->clientnum) addmsg(SV_SPECTCN, "ri", available[idx]->clientnum); player1->followplayercn = available[idx]->clientnum; return players[player1->followplayercn]; } @@ -1123,6 +1194,7 @@ playerent *updatefollowplayer(int shiftdirection) void spectate(int mode) { if(!player1->isspectating()) return; + if(!m_teammode && !team_isspect(player1->team) && servstate.mastermode == MM_MATCH) return; // during ffa matches only SPECTATORS can spectate if(mode == player1->spectatemode) return; showscores(false); switch(mode) @@ -1138,6 +1210,9 @@ void spectate(int mode) { if(player1->spectatemode != SM_FLY) { + player1->followplayercn = FPCN_FLY; + addmsg(SV_SPECTCN, "ri", player1->followplayercn); +#if 0 // FIXME - we can't call updatefollowplayer() here, because that would change followplayercn and send a SV_SPECTCN // set spectator location to last followed player playerent *f = updatefollowplayer(); if(f) @@ -1148,6 +1223,10 @@ void spectate(int mode) player1->resetinterp(); } else entinmap(player1); // or drop 'em at a random place +#else + entinmap(player1); // place near last location +#endif + } break; } @@ -1174,7 +1253,7 @@ void changefollowplayer(int shift) updatefollowplayer(shift); } -COMMAND(spectate, ARG_1INT); +COMMANDN(spectatemode, spectate, ARG_1INT); COMMAND(togglespect, ARG_NONE); COMMAND(changefollowplayer, ARG_1INT); diff --git a/source/src/clients2c.cpp b/source/src/clients2c.cpp index 3cbd9b6e..0f4699ee 100644 --- a/source/src/clients2c.cpp +++ b/source/src/clients2c.cpp @@ -26,10 +26,12 @@ void neterr(const char *s) VARP(autogetmap, 0, 1, 1); // only if the client doesn't have that map VARP(autogetnewmaprevisions, 0, 1, 1); -void changemapserv(char *name, int mode, int download, int revision) // forced map change from the server +bool localwrongmap = false; + +bool changemapserv(char *name, int mode, int download, int revision) // forced map change from the server { gamemode = mode; - if(m_demo) return; + if(m_demo) return true; bool loaded = load_world(name); if(download > 0) { @@ -40,9 +42,9 @@ void changemapserv(char *name, int mode, int download, int revision) // f } else { - if(securemapcheck(name, false)) return; + if(securemapcheck(name, false)) return true; bool sizematch = maploaded == download || download < 10; - if(loaded && sizematch && revmatch) return; + if(loaded && sizematch && revmatch) return true; bool getnewrev = autogetnewmaprevisions && revision > hdr.maprevision; if(autogetmap || getnewrev) { @@ -62,6 +64,8 @@ void changemapserv(char *name, int mode, int download, int revision) // f } } } + else return true; + return false; } // update the position of other clients in the game in our world @@ -205,14 +209,6 @@ void parsemessages(int cn, playerent *d, ucharbuf &p) if(prot!=CUR_PROTOCOL_VERSION && !(watchingdemo && prot == -PROTOCOL_VERSION)) { conoutf("\f3you are using a different game protocol (you: %d, server: %d)", CUR_PROTOCOL_VERSION, prot); -#if 0 - if(prot<1000) // bug, possibly - { - // might indicate a client/server communication bug, create error report - pktlogger.flushtolog("packetlog_protocol.txt"); - conoutf("\f3wrote a network error report to packetlog_protocol.txt, please post this file to the bugtracker now!"); - } -#endif disconnect(); return; } @@ -245,16 +241,17 @@ void parsemessages(int cn, playerent *d, ucharbuf &p) { playerent *d = getclient(getint(p)); if(d) d->lastvoicecom = lastmillis; - audiomgr.playsound(getint(p), SP_HIGH); + if(!d || !(d->muted || d->ignored)) audiomgr.playsound(getint(p), SP_HIGH); break; } case SV_VOICECOM: { - audiomgr.playsound(getint(p), SP_HIGH); + if(!d || !(d->muted || d->ignored)) audiomgr.playsound(getint(p), SP_HIGH); if(d) d->lastvoicecom = lastmillis; break; } + case SV_TEAMTEXTME: case SV_TEAMTEXT: { int cn = getint(p); @@ -262,11 +259,16 @@ void parsemessages(int cn, playerent *d, ucharbuf &p) filtertext(text, text); playerent *d = getclient(cn); if(!d) break; - if(m_teammode) conoutf("%s:\f1 %s", colorname(d), text); - else conoutf("%s:\f0 %s", colorname(d), text); + if(d->ignored) clientlogf("ignored: %s%s %s", colorname(d), type == SV_TEAMTEXT ? ":" : "", text); + else + { + if(m_teammode) conoutf(type == SV_TEAMTEXTME ? "\f1%s %s" : "%s:\f1 %s", colorname(d), highlight(text)); + else conoutf(type == SV_TEAMTEXTME ? "\f0%s %s" : "%s:\f0 %s", colorname(d), highlight(text)); + } break; } + case SV_TEXTME: case SV_TEXT: if(cn == -1) { @@ -278,18 +280,21 @@ void parsemessages(int cn, playerent *d, ucharbuf &p) { getstring(text, p); filtertext(text, text); - conoutf("%s:\f0 %s", colorname(d), text); + if(d->ignored && d->clientrole != CR_ADMIN) clientlogf("ignored: %s%s %s", colorname(d), type == SV_TEXT ? ":" : "", text); + else conoutf(type == SV_TEXTME ? "\f0%s %s" : "%s:\f0 %s", colorname(d), highlight(text)); } else return; break; case SV_MAPCHANGE: { + extern int spawnpermission; + spawnpermission = SP_WRONGMAP; getstring(text, p); int mode = getint(p); int downloadable = getint(p); int revision = getint(p); - changemapserv(text, mode, downloadable, revision); + localwrongmap = !changemapserv(text, mode, downloadable, revision); if(m_arena && joining>2) deathstate(player1); break; } @@ -325,10 +330,8 @@ void parsemessages(int cn, playerent *d, ucharbuf &p) conoutf("connected: %s", colorname(d, text)); } s_strncpy(d->name, text, MAXNAMELEN+1); - getstring(text, p); - d->team = getint(p); - if(!team_isvalid(d->team)) d->team = TEAM_VOID; - setskin(d, getint(p)); + d->setskin(TEAM_CLA, getint(p)); + d->setskin(TEAM_RVSF, getint(p)); if(m_flags) loopi(2) { flaginfo &f = flaginfos[i]; @@ -390,10 +393,8 @@ void parsemessages(int cn, playerent *d, ucharbuf &p) loopi(NUMGUNS) player1->mag[i] = getint(p); player1->state = CS_ALIVE; findplayerstart(player1, false, arenaspawn); - extern void nextskin_player1(); - nextskin_player1(); arenaintermission = 0; - if(m_arena) + if(m_arena && !localwrongmap) { closemenu(NULL); conoutf("new round starting... fight!"); @@ -533,6 +534,22 @@ void parsemessages(int cn, playerent *d, ucharbuf &p) break; } + case SV_DISCSCORES: + { + discscores.setsize(0); + int team; + while((team = getint(p)) >= 0) + { + discscore &ds = discscores.add(); + ds.team = team; + getstring(text, p); + filtertext(ds.name, text, 0, MAXNAMELEN); + ds.flags = getint(p); + ds.frags = getint(p); + ds.deaths = getint(p); + } + break; + } case SV_ITEMSPAWN: { int i = getint(p); @@ -694,6 +711,36 @@ void parsemessages(int cn, playerent *d, ucharbuf &p) break; } + case SV_LMSITEM: + { + const char *names[] = { "no items", "pistol clips", "ammoboxes", "grenades", "health packs", "armour", "akimbo" }; + const int types[] = { NOTUSED, I_CLIPS, I_AMMO, I_GRENADE, I_HEALTH, I_ARMOUR, I_AKIMBO }, types_n = sizeof(types) / sizeof(types[0]); + int cmd = getint(p), typ = getint(p); + int n = 0; + loopi(types_n) if(typ == types[i]) n = i; + switch(cmd) + { + case LMSITEM_CLEAR: + resetspawns(n > 0 ? typ : -1); + break; + case LMSITEM_ANNOUNCE: + hudoutf("%s will spawn%s", names[n], typ != NOTUSED ? " in 10 seconds" : ""); // sound? + break; + case LMSITEM_SPAWN: + loopv(ents) if(ents[i].type == typ) setspawn(i, true); + hudonlyf("\f3%s spawned", names[n]); // sound? + break; + } + break; + } + + case SV_SPAWNDENY: + { + extern int spawnpermission; + spawnpermission = getint(p); + if(spawnpermission == SP_REFILLMATCH) hudoutf("\f3You can now spawn to refill your team."); + break; + } case SV_FORCEDEATH: { int cn = getint(p); @@ -721,41 +768,74 @@ void parsemessages(int cn, playerent *d, ucharbuf &p) break; } - case SV_FORCETEAM: + case SV_TEAMDENY: { - int team = getint(p), oldteam = player1->team; - int attr = getint(p); - bool respawn = (attr & 1) == 1; - changeteam(team, respawn); - if(attr & 2) hudoutf("you %s team %s", team == oldteam ? "stay in" : "got forced to", team_string(team)); + int t = getint(p); + if(team_isvalid(t)) conoutf("you can't change to team %s", team_string(t)); break; } - case SV_FORCENOTIFY: + case SV_SETTEAM: { - int fpl = getint(p); - int fnt = getint(p); - playerent *d = getclient(fpl); - bool you = fpl == player1->clientnum; // sound? - bool et = team_base(player1->team) != team_base(fnt); - hudoutf("the server forced %s to%s team%s", you ? "you" : d ? colorname(d) : "", you ? "" : et ? " the enemy" : " your", you ? fnt ? " RVSF": " CLA" : ""); - break; - } - - case SV_AUTOTEAM: - { - int na = getint(p); - switch(na) + int fpl = getint(p), fnt = getint(p), ftr = fnt >> 4; + fnt &= 0x0f; + playerent *d = (fpl == getclientnum() ? player1 : newclient(fpl)); + if(d) { - case AT_ENABLED: - case AT_DISABLED: - autoteambalance = na == AT_ENABLED; - break; - case AT_SHUFFLE: // sound? - break; + const char *nts = team_string(fnt); + bool you = fpl == player1->clientnum; + if(m_teammode || team_isspect(fnt)) + { + if(d->team == fnt) + { + if(you && ftr == FTR_AUTOTEAM) hudoutf("you stay in team %s", nts); + } + else + { + if(you && !watchingdemo) + { + switch(ftr) + { + case FTR_PLAYERWISH: + conoutf("you're now in team %s", nts); + break; + case FTR_AUTOTEAM: + hudoutf("the server forced you to team %s", nts); + break; + } + } + else + { + const char *pls = colorname(d); + bool et = team_base(player1->team) != team_base(fnt); + switch(ftr) + { + case FTR_PLAYERWISH: + conoutf("player %s switched to team %s", pls, nts); // new message + break; + case FTR_AUTOTEAM: + if(watchingdemo) conoutf("the server forced %s to team %s", colorname(d), nts); + else hudoutf("the server forced %s to %s team", colorname(d), et ? "the enemy" : "your"); + break; + } + } + if(you && !team_isspect(d->team) && team_isspect(fnt) && d->state == CS_DEAD) spectate(SM_FLY); + } + } + d->team = fnt; } break; } + + case SV_SERVERMODE: + { + int sm = getint(p); + servstate.autoteam = sm & 1; + servstate.mastermode = (sm >> 2) & MM_MASK; + servstate.matchteamsize = sm >> 4; + //if(sm & AT_SHUFFLE) playsound(TEAMSHUFFLE); // TODO + break; + } case SV_CALLVOTE: { int type = getint(p); @@ -937,5 +1017,6 @@ void servertoclient(int chan, uchar *buf, int len) // processes any updates fr void localservertoclient(int chan, uchar *buf, int len) // processes any updates from the server { +// pktlogger.queue(enet_packet_create (buf, len, 0)); // log local & demo packets servertoclient(chan, buf, len); } diff --git a/source/src/console.cpp b/source/src/console.cpp index 891dd58c..e633408d 100644 --- a/source/src/console.cpp +++ b/source/src/console.cpp @@ -21,10 +21,10 @@ struct console : consolebuffer static const int WORDWRAP = 80; int fullconsole; - void toggleconsole() + void toggleconsole() { if(!fullconsole) fullconsole = altconsize ? 1 : 2; - else fullconsole = ++fullconsole % 3; + else fullconsole = ++fullconsole % 3; } void addline(const char *sf) { consolebuffer::addline(sf, totalmillis); } @@ -34,7 +34,7 @@ struct console : consolebuffer int conwidth = (fullconsole ? VIRTW : int(floor(getradarpos().x)))*2 - 2*CONSPAD - 2*FONTH/3; int h = VIRTH*2 - 2*CONSPAD - 2*FONTH/3; int conheight = min(fullconsole ? (h*(fullconsole==1 ? altconsize : fullconsize))/100 : FONTH*consize, h); - + if(fullconsole) blendbox(CONSPAD, CONSPAD, conwidth+CONSPAD+2*FONTH/3, conheight+CONSPAD+2*FONTH/3, true); int numl = conlines.length(), offset = min(conskip, numl); @@ -90,13 +90,26 @@ COMMANDN(toggleconsole, toggleconsole, ARG_NONE); void renderconsole() { con.render(); } +void clientlogf(const char *s, ...) +{ + s_sprintfdv(sp, s); + filtertext(sp, sp, 2); + extern struct servercommandline scl; + const char *ts = scl.logtimestamp ? timestring(true, "%b %d %H:%M:%S ") : ""; + char *p, *l = sp; + do + { // break into single lines first + if((p = strchr(l, '\n'))) *p = '\0'; + printf("%s%s\n", ts, l); + l = p + 1; + } + while(p); +} + void conoutf(const char *s, ...) { s_sprintfdv(sf, s); - string sp; - filtertext(sp, sf, 2); - extern struct servercommandline scl; - printf("%s%s\n", scl.logtimestamp ? timestring(true, "%b %d %H:%M:%S ") : "", sp); + clientlogf("%s", sf); con.addline(sf); } diff --git a/source/src/docs.cpp b/source/src/docs.cpp index f7162e68..d7ac4269 100644 --- a/source/src/docs.cpp +++ b/source/src/docs.cpp @@ -179,36 +179,45 @@ char *cvecstr(vector &cvec, const char *substr, int *rline = NULL) return r; } +void listundoneidents(vector &inames, int allidents) +{ + enumeratekt(*idents, const char *, name, ident, id, + { + if((allidents > 0) || id.type != ID_ALIAS) + { + docident *id = docidents.access(name); + if(id) // search for substrings that indicate undoneness + { + cvector srch; + srch.add(id->name); + srch.add(id->desc); + loopvj(id->remarks) srch.add(id->remarks[j]); + loopvj(id->arguments) + { + srch.add(id->arguments[j].token); + srch.add(id->arguments[j].desc); + srch.add(id->arguments[j].values); + } + loopvj(id->references) + { + srch.add(id->references[j].ident); + srch.add(id->references[j].name); + srch.add(id->references[j].url); + } + if(cvecstr(srch, "TODO") || cvecstr(srch, "UNDONE")) id = NULL; + } + if(!id) inames.add(name); + } + }); + inames.sort(stringsort); +} + void docundone(int allidents) { vector inames; - identnames(inames, !(allidents > 0)); + listundoneidents(inames, allidents); inames.sort(stringsort); - loopv(inames) - { - docident *id = docidents.access(inames[i]); - if(id) // search for substrings that indicate undoneness - { - cvector srch; - srch.add(id->name); - srch.add(id->desc); - loopvj(id->remarks) srch.add(id->remarks[j]); - loopvj(id->arguments) - { - srch.add(id->arguments[j].token); - srch.add(id->arguments[j].desc); - srch.add(id->arguments[j].values); - } - loopvj(id->references) - { - srch.add(id->references[j].ident); - srch.add(id->references[j].name); - srch.add(id->references[j].url); - } - if(!cvecstr(srch, "TODO") && !cvecstr(srch, "UNDONE")) continue; - } - conoutf("%s", inames[i]); - } + loopv(inames) conoutf("%s", inames[i]); } void docinvalid() @@ -273,72 +282,101 @@ char *xmlstringenc(char *d, const char *s, size_t len) return d; } -void docwritebaseref(char *ref, char *schemalocation, char *transformation) +void docwriteref(int allidents, const char *ref, const char *schemalocation, const char *transformation) { - FILE *f = openfile(path("docs/autogenerated_base_reference.xml", true), "w"); + FILE *f = openfile(path(allidents < 0 ? "docs/autogenerated_base_reference.xml" : "docs/autogenerated_todo_reference.xml", true), "w"); if(!f) return; char desc[] = "TODO: Description"; - fprintf(f, "\n"); - fprintf(f, "\n", transformation && strlen(transformation) ? transformation : "transformations/cuberef2xhtml.xslt"); - fprintf(f, "\n", ref && strlen(ref) ? ref : "Unnamed Reference", schemalocation && strlen(schemalocation) ? schemalocation : "http://cubers.net/Schemas/CubeRef schemas/cuberef.xsd"); - fprintf(f, "\t%s\n", desc); - fprintf(f, "\t\n"); - fprintf(f, "\t\t
    \n"); - fprintf(f, "\t\t\t%s\n", desc); - fprintf(f, "\t\t\t\n"); + vector inames; + if(allidents < 0) identnames(inames, true); // -1: all builtin + else listundoneidents(inames, allidents); // 0: todo builtin, 1: todo builtin + alias + inames.sort(stringsort); + + if(allidents < 0) + { + fprintf(f, "\n"); + fprintf(f, "\n", transformation && strlen(transformation) ? transformation : "transformations/cuberef2xhtml.xslt"); + fprintf(f, "\n", ref && strlen(ref) ? ref : "Unnamed Reference", schemalocation && strlen(schemalocation) ? schemalocation : "http://cubers.net/Schemas/CubeRef schemas/cuberef.xsd"); + fprintf(f, "\t%s\n", desc); + fprintf(f, "\t\n"); + fprintf(f, "\t\t
    \n"); + fprintf(f, "\t\t\t%s\n", desc); + fprintf(f, "\t\t\t\n"); + } static const int bases[] = { ARG_1INT, ARG_1STR, ARG_1EXP, ARG_1EXPF, ARG_1EST }; string name; - enumerate(*idents, ident, id, + loopv(inames) { - if(id.type != ID_COMMAND) continue; - fprintf(f, "\t\t\t\t\n", xmlstringenc(name, id.name, _MAXDEFSTR)); + ident *id = idents->access(inames[i]); + if(!id || id->type != ID_COMMAND) continue; + fprintf(f, "\t\t\t\t\n", xmlstringenc(name, id->name, _MAXDEFSTR)); fprintf(f, "\t\t\t\t\t%s\n", desc); - if(id.narg != ARG_NONE && id.narg != ARG_DOWN && id.narg != ARG_IVAL && id.narg != ARG_FVAL && id.narg != ARG_SVAL) + if(id->narg != ARG_NONE && id->narg != ARG_DOWN && id->narg != ARG_IVAL && id->narg != ARG_FVAL && id->narg != ARG_SVAL) { fprintf(f, "\t\t\t\t\t\n"); - if(id.narg == ARG_CONC || id.narg == ARG_CONCW || id.narg == ARG_VARI) fprintf(f, "\t\t\t\t\t\t\n"); + if(id->narg == ARG_CONC || id->narg == ARG_CONCW || id->narg == ARG_VARI) fprintf(f, "\t\t\t\t\t\t\n"); else { int base = -1; loopj(sizeof(bases)/sizeof(bases[0])) { - if(id.narg < bases[j]) { if(j) base = bases[j-1]; break; } + if(id->narg < bases[j]) { if(j) base = bases[j-1]; break; } } - if(base >= 0) loopj(id.narg-base+1) fprintf(f, "\t\t\t\t\t\t\n", (char)(*"A")+j); + if(base >= 0) loopj(id->narg-base+1) fprintf(f, "\t\t\t\t\t\t\n", (char)(*"A")+j); } fprintf(f, "\t\t\t\t\t\n"); } fprintf(f, "\t\t\t\t\n"); - }); - enumerate(*idents, ident, id, + } + loopv(inames) { - if(id.type != ID_VAR && id.type != ID_FVAR) continue; - fprintf(f, "\t\t\t\t\n", xmlstringenc(name, id.name, _MAXDEFSTR)); + ident *id = idents->access(inames[i]); + if(!id || (id->type != ID_VAR && id->type != ID_FVAR)) continue; + fprintf(f, "\t\t\t\t\n", xmlstringenc(name, id->name, _MAXDEFSTR)); fprintf(f, "\t\t\t\t\tTODO\n"); - switch(id.type) + switch(id->type) { case ID_VAR: - fprintf(f, "\t\t\t\t\t\n", id.minval>id.maxval ? "" : "token=\"N\"", id.minval, id.maxval, *id.storage.i, id.minval>id.maxval ? "readOnly=\"true\"" : ""); + fprintf(f, "\t\t\t\t\t\n", id->minval>id->maxval ? "" : "token=\"N\"", id->minval, id->maxval, *id->storage.i, id->minval>id->maxval ? "readOnly=\"true\"" : ""); break; case ID_FVAR: - fprintf(f, "\t\t\t\t\t\n", id.minvalf>id.maxvalf ? "" : "token=\"N\"", - floatstr(id.minvalf), floatstr(id.maxvalf), floatstr(*id.storage.f), - id.minvalf>id.maxvalf ? "readOnly=\"true\"" : ""); + fprintf(f, "\t\t\t\t\t\n", id->minvalf>id->maxvalf ? "" : "token=\"N\"", + floatstr(id->minvalf), floatstr(id->maxvalf), floatstr(*id->storage.f), + id->minvalf>id->maxvalf ? "readOnly=\"true\"" : ""); break; } fprintf(f, "\t\t\t\t\n"); - }); + } + loopv(inames) + { + ident *id = idents->access(inames[i]); + if(!id || id->type != ID_ALIAS) continue; + fprintf(f, "\t\t\t\t\n", xmlstringenc(name, id->name, _MAXDEFSTR)); + fprintf(f, "\t\t\t\t\t%s\n", desc); + fprintf(f, "\t\t\t\t\n"); + } - fprintf(f, "\t\t\t\n\t\t
    \n\t
    \n
    \n"); + if(allidents < 0) fprintf(f, "\t\t\t
    \n\t\t
    \n\t
    \n
    \n"); fclose(f); } +void docwritebaseref(char *ref, char *schemalocation, char *transformation) +{ + docwriteref(-1, ref, schemalocation, transformation); +} + +void docwritetodoref(int allidents) +{ + docwriteref(allidents ? 1 : 0, "", "", ""); +} + COMMAND(docundone, ARG_1INT); COMMAND(docinvalid, ARG_NONE); COMMAND(docfind, ARG_1STR); COMMAND(docwritebaseref, ARG_3STR); +COMMAND(docwritetodoref, ARG_1INT); VAR(docvisible, 0, 1, 1); VAR(docskip, 0, 0, 1000); diff --git a/source/src/editing.cpp b/source/src/editing.cpp index 469ec9ee..ff1416b3 100644 --- a/source/src/editing.cpp +++ b/source/src/editing.cpp @@ -487,6 +487,94 @@ void newent(char *what, char *a1, char *a2, char *a3, char *a4) newentity(-1, sel.x, sel.y, (int)camera1->o.z, what, ATOI(a1), ATOI(a2), ATOI(a3), ATOI(a4)); } +void movemap(int xo, int yo, int zo) // move whole map +{ + EDITMP; + if(!worldbordercheck(MINBORD + max(-xo, 0), MINBORD + max(xo, 0), MINBORD + max(-yo, 0), MINBORD + max(yo, 0), max(zo, 0), max(-zo, 0))) + { + conoutf("not enough space to move the map"); + return; + } + if(xo || yo) + { + block b = { max(-xo, 0), max(-yo, 0), ssize - abs(xo), ssize - abs(yo) }, *cp = blockcopy(b); + cp->x = max(xo, 0); + cp->y = max(yo, 0); + blockpaste(*cp); + freeblock(cp); + } + if(zo) + { + loop(x, ssize) loop(y, ssize) + { + S(x,y)->floor = max(-128, S(x,y)->floor + zo); + S(x,y)->ceil = min(127, S(x,y)->ceil + zo); + } + hdr.waterlevel += zo; + } + loopv(ents) + { + ents[i].x += xo; + ents[i].y += yo; + ents[i].z += zo; + if(OUTBORD(ents[i].x, ents[i].y)) ents[i].type = NOTUSED; + } + player1->o.x += xo; + player1->o.y += yo; + entinmap(player1); + calclight(); + resetmap(false); +} + +void selfliprotate(int dir) +{ + makeundo(); + block *org = blockcopy(sel); + const sqr *q = (const sqr *)(org + 1); + int x1 = sel.x, x2 = sel.x + sel.xs - 1, y1 = sel.y, y2 = sel.y + sel.ys - 1, x, y; + switch(dir) + { + case 1: // 90 degree clockwise + for(x = x2; x >= x1; x--) for(y = y1; y <= y2; y++) *S(x,y) = *q++; + break; + case 2: // 180 degree + for(y = y2; y >= y1; y--) for(x = x2; x >= x1; x--) *S(x,y) = *q++; + break; + case 3: // 90 degree counterclockwise + for(x = x1; x <= x2; x++) for(y = y2; y >= y1; y--) *S(x,y) = *q++; + break; + case 11: // flip x-axis + for(y = y1; y <= y2; y++) for(x = x2; x >= x1; x--) *S(x,y) = *q++; + break; + case 12: // flip y-axis + for(y = y2; y >= y1; y--) for(x = x1; x <= x2; x++) *S(x,y) = *q++; + break; + } + remipmore(sel); + freeblock(org); +} + +void selectionrotate(int dir) +{ + EDITSELMP; + dir %= 4; + if(dir < 0) dir += 4; + if(!dir) return; + if(sel.xs != sel.ys) dir = 2; + selfliprotate(dir); +} + +void selectionflip(char *axis) +{ + EDITSELMP; + if(!axis || !*axis) return; + switch(toupper(*axis)) + { + case 'X': selfliprotate(11); break; + case 'Y': selfliprotate(12); break; + } +} + COMMANDN(select, selectpos, ARG_4INT); COMMAND(edittag, ARG_1INT); COMMAND(replace, ARG_NONE); @@ -500,5 +588,6 @@ COMMAND(paste, ARG_NONE); COMMAND(edittex, ARG_2INT); COMMAND(newent, ARG_5STR); COMMAND(perlin, ARG_3INT); - - +COMMAND(movemap, ARG_3INT); +COMMAND(selectionrotate, ARG_1INT); +COMMAND(selectionflip, ARG_1STR); diff --git a/source/src/entities.cpp b/source/src/entities.cpp index 9f4a9eef..543fdc0f 100644 --- a/source/src/entities.cpp +++ b/source/src/entities.cpp @@ -5,16 +5,6 @@ vector ents; -const char *entnames[] = -{ - "none?", "light", "playerstart", - "pistol", "ammobox","grenades", - "health", "armour", "akimbo", - "mapmodel", "trigger", - "ladder", "ctf-flag", - "sound", "clip", "", "" -}; - const char *entmdlnames[] = { "pickups/pistolclips", "pickups/ammobox", "pickups/nades", "pickups/health", "pickups/kevlar", "pickups/akimbo", @@ -28,15 +18,19 @@ void renderent(entity &e) rendermodel(mdlname, ANIM_MAPMODEL|ANIM_LOOP|ANIM_DYNALLOC, 0, 0, vec(e.x, e.y, z+S(e.x, e.y)->floor+e.attr1), yaw, 0); } -void renderclip(entity &e, bool ismm = false) +void renderclip(entity &e) { float xradius = max(float(e.attr2), 0.1f), yradius = max(float(e.attr3), 0.1f); vec bbmin(e.x - xradius, e.y - yradius, float(S(e.x, e.y)->floor+e.attr1)), bbmax(e.x + xradius, e.y + yradius, bbmin.z + max(float(e.attr4), 0.1f)); glDisable(GL_TEXTURE_2D); - if(ismm) linestyle(1, 0, 0xFF, 0); - else linestyle(1, 0xFF, 0xFF, 0); + switch(e.type) + { + case CLIP: linestyle(1, 0xFF, 0xFF, 0); break; // yellow + case MAPMODEL: linestyle(1, 0, 0xFF, 0); break; // green + case PLCLIP: linestyle(1, 0xFF, 0, 0xFF); break; // magenta + } glBegin(GL_LINES); glVertex3f(bbmin.x, bbmin.y, bbmin.z); @@ -109,18 +103,18 @@ void renderentities() s_sprintfd(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), (float)((e.attr1+7)-(e.attr1+7)%15), 0, 120.0f); } - else if(e.type==CLIP && !stenciling) renderclip(e); + else if((e.type == CLIP || e.type == PLCLIP) && !stenciling) renderclip(e); else if(showmodelclipping && e.type == MAPMODEL && !stenciling) { mapmodelinfo &mmi = getmminfo(e.attr2); if(&mmi && mmi.h) { entity ce = e; - ce.type = CLIP; + ce.type = MAPMODEL; ce.attr1 = mmi.zoff+e.attr3; ce.attr2 = ce.attr3 = mmi.rad; ce.attr4 = mmi.h; - renderclip(ce, true); + renderclip(ce); } } } @@ -297,14 +291,15 @@ void spawnallitems() // spawns items locally } } -void resetspawns() +void resetspawns(int type) { - loopv(ents) ents[i].spawned = false; + loopv(ents) if(type < 0 || type == ents[i].type) ents[i].spawned = false; if(m_noitemsnade || m_pistol) { loopv(ents) ents[i].transformtype(gamemode); } } + void setspawn(int i, bool on) { if(ents.inrange(i)) ents[i].spawned = on; } bool selectnextprimary(int num) diff --git a/source/src/entity.h b/source/src/entity.h index 24a49c3e..46b83dfd 100644 --- a/source/src/entity.h +++ b/source/src/entity.h @@ -1,3 +1,13 @@ +#ifdef SERVER_CPP + #define __ext + #define __init(...) = __VA_ARGS__; // i hope, all used compilers support variadic macros ;) + #define __initf(...) __VA_ARGS__ +#else + #define __ext extern + #define __init(...) ; + #define __initf(...) ; +#endif + enum // static entity types { NOTUSED = 0, // entity slot not in use in map @@ -11,9 +21,20 @@ enum // static entity types CTF_FLAG, // attr1 = angle, attr2 = red/blue SOUND, CLIP, + PLCLIP, MAXENTTYPES }; +__ext const char *entnames[] __init( +{ + "none?", "light", "playerstart", + "pistol", "ammobox","grenades", + "health", "armour", "akimbo", + "mapmodel", "trigger", + "ladder", "ctf-flag", + "sound", "clip", "plclip", "", "" +}) + #define isitem(i) ((i) >= I_CLIPS && (i) <= I_AKIMBO) struct persistent_entity // map entity @@ -40,7 +61,7 @@ struct entity : public persistent_entity }; struct itemstat { int add, start, max, sound; }; -static itemstat ammostats[] = +__ext itemstat ammostats[] __init( { {1, 1, 1, S_ITEMAMMO}, //knife dummy {16, 32, 72, S_ITEMAMMO}, //pistol @@ -50,13 +71,13 @@ static itemstat ammostats[] = {30, 60, 60, S_ITEMAMMO}, //assault {2, 0, 2, S_ITEMAMMO}, //grenade {72, 0, 72, S_ITEMAKIMBO} //akimbo -}; +}) -static itemstat powerupstats[] = +__ext itemstat powerupstats[] __init( { {33, 100, 100, S_ITEMHEALTH}, //health {50, 100, 100, S_ITEMARMOUR}, //armour -}; +}) enum { GUN_KNIFE = 0, GUN_PISTOL, GUN_SHOTGUN, GUN_SUBGUN, GUN_SNIPER, GUN_ASSAULT, GUN_GRENADE, GUN_AKIMBO, NUMGUNS }; #define reloadable_gun(g) (g != GUN_KNIFE && g != GUN_GRENADE) @@ -66,7 +87,7 @@ enum { GUN_KNIFE = 0, GUN_PISTOL, GUN_SHOTGUN, GUN_SUBGUN, GUN_SNIPER, GUN_ASSAU #define EXPDAMRAD 10 struct guninfo { string modelname; short sound, reload, reloadtime, attackdelay, damage, projspeed, part, spread, recoil, magsize, mdl_kick_rot, mdl_kick_back, recoilincrease, recoilbase, maxrecoil, recoilbackfade, pushfactor; bool isauto; }; -static guninfo guns[NUMGUNS] = +__ext guninfo guns[NUMGUNS] __init( { { "knife", S_KNIFE, S_NULL, 0, 500, 50, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, false }, { "pistol", S_PISTOL, S_RPISTOL, 1400, 170, 19, 0, 0, 80, 10, 8, 6, 5, 1, 40, 75, 150, 1, false }, @@ -76,15 +97,15 @@ static guninfo guns[NUMGUNS] = { "assault", S_ASSAULT, S_RASSAULT, 2000, 130, 24, 0, 0, 20, 40, 15, 0, 2, 2, 25, 60, 150, 1, true }, { "grenade", S_NULL, S_NULL, 1000, 650, 200, 20, 6, 1, 1, 1, 3, 1, 0, 0, 0, 0, 3, false }, { "pistol", S_PISTOL, S_RAKIMBO, 1400, 80, 19, 0, 0, 80, 10, 16, 6, 5, 4, 30, 75, 150, 1, true }, -}; +}) -static inline int reloadtime(int gun) { return guns[gun].reloadtime; } -static inline int attackdelay(int gun) { return guns[gun].attackdelay; } -static inline int magsize(int gun) { return guns[gun].magsize; } +__ext int reloadtime(int gun) __initf({ return guns[gun].reloadtime; }) +__ext int attackdelay(int gun) __initf({ return guns[gun].attackdelay; }) +__ext int magsize(int gun) __initf({ return guns[gun].magsize; }) -enum { TEAM_CLA = 0, TEAM_RVSF, TEAM_CLA_SPECT, TEAM_RVSF_SPECT, TEAM_SPECT, TEAM_NUM }; -static const char *teamnames[] = {"CLA", "RVSF", "CLA-SPECT", "RVSF-SPECT", "SPECTATOR", "void"}; -static const char *teamnames_s[] = {"CLA", "RVSF", "CSPC", "RSPC", "SPEC", "void"}; +enum { TEAM_CLA = 0, TEAM_RVSF, TEAM_CLA_SPECT, TEAM_RVSF_SPECT, TEAM_SPECT, TEAM_NUM, TEAM_ANYACTIVE }; +__ext const char *teamnames[] __init({"CLA", "RVSF", "CLA-SPECT", "RVSF-SPECT", "SPECTATOR", "void"}) +__ext const char *teamnames_s[] __init({"CLA", "RVSF", "CSPC", "RSPC", "SPEC", "void"}) #define TEAM_VOID TEAM_NUM #define isteam(a,b) (m_teammode && (a) == (b)) @@ -93,14 +114,17 @@ static const char *teamnames_s[] = {"CLA", "RVSF", "CSPC", "RSPC", "SPEC", "void #define team_basestring(t) ((t) == 1 ? teamnames[1] : ((t) == 0 ? teamnames[0] : "SPECT")) #define team_isvalid(t) ((int(t)) >= 0 && (t) < TEAM_NUM) #define team_isactive(t) ((t) == TEAM_CLA || (t) == TEAM_RVSF) -#define team_isspect(t) ((t) > 1) +#define team_isspect(t) ((t) > TEAM_RVSF && (t) < TEAM_VOID) +#define team_group(t) ((t) == TEAM_SPECT ? TEAM_SPECT : team_base(t)) +#define team_tospec(t) ((t) == TEAM_SPECT ? TEAM_SPECT : team_base(t) + TEAM_CLA_SPECT - TEAM_CLA) // note: team_isactive and team_base can/should be used to check the limits for arrays of size '2' -static inline const char *team_string(int t, bool abbr = false) { const char **n = abbr ? teamnames_s : teamnames; return team_isvalid(t) ? n[t] : n[TEAM_NUM]; } +__ext const char *team_string(int t, bool abbr = false) __initf({ const char **n = abbr ? teamnames_s : teamnames; return team_isvalid(t) ? n[t] : n[TEAM_NUM]; }) 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 { SM_NONE = 0, SM_DEATHCAM, SM_FOLLOW1ST, SM_FOLLOW3RD, SM_FOLLOW3RD_TRANSPARENT, SM_FLY, SM_NUM }; +enum { FPCN_VOID = -3, FPCN_DEATHCAM = -2, FPCN_FLY = -1 }; class worldobject { @@ -352,6 +376,8 @@ public: class playerent : public dynent, public playerstate { +private: + int curskin, nextskin[2]; public: int clientnum, lastupdate, plag, ping; int lifesequence; // sequence id for each respawn, used in damage test @@ -363,7 +389,6 @@ public: int team; int weaponchanging; int nextweapon; // weapon we switch to - int skin; int spectatemode, followplayercn; int eardamagemillis; int respawnoffset; @@ -381,11 +406,13 @@ public: vec head; - playerent() : clientnum(-1), lastupdate(0), plag(0), ping(0), lifesequence(0), frags(0), flagscore(0), deaths(0), lastpain(0), lastvoicecom(0), clientrole(CR_DEFAULT), - team(0), skin(0), spectatemode(SM_NONE), followplayercn(-1), eardamagemillis(0), respawnoffset(0), + bool ignored, muted; + + playerent() : curskin(0), clientnum(-1), lastupdate(0), plag(0), ping(0), lifesequence(0), frags(0), flagscore(0), deaths(0), lastpain(0), lastvoicecom(0), clientrole(CR_DEFAULT), + team(TEAM_SPECT), spectatemode(SM_NONE), followplayercn(FPCN_VOID), eardamagemillis(0), respawnoffset(0), prevweaponsel(NULL), weaponsel(NULL), nextweaponsel(NULL), primweap(NULL), nextprimweap(NULL), lastattackweapon(NULL), smoothmillis(-1), - head(-1, -1, -1) + head(-1, -1, -1), ignored(false), muted(false) { type = ENT_PLAYER; name[0] = 0; @@ -394,6 +421,7 @@ public: radius = 1.1f; maxspeed = 16.0f; skin_noteam = skin_cla = skin_rvsf = NULL; + loopi(2) nextskin[i] = 0; respawn(); } @@ -434,7 +462,7 @@ public: void resetspec() { spectatemode = SM_NONE; - followplayercn = -1; + followplayercn = FPCN_VOID; } void respawn() @@ -450,6 +478,7 @@ public: resetspec(); eardamagemillis = 0; eyeheight = maxeyeheight; + curskin = nextskin[team_base(team)]; } void spawnstate(int gamemode) @@ -458,6 +487,7 @@ public: prevweaponsel = weaponsel = weapons[gunselect]; primweap = weapons[primary]; nextprimweap = weapons[nextprimary]; + curskin = nextskin[team_base(team)]; } void selectweapon(int w) { prevweaponsel = weaponsel = weapons[(gunselect = w)]; } @@ -474,6 +504,13 @@ public: nextweaponsel = w; w->onselecting(); } + int skin(int t = -1) { return nextskin[team_base(t < 0 ? team : t)]; } + void setskin(int t, int s) + { + const int maxskin[2] = { 4, 6 }; + t = team_base(t < 0 ? team : t); + nextskin[t] = abs(s) % maxskin[t]; + } }; @@ -569,3 +606,6 @@ public: void onmoved(const vec &dist); }; +#undef __ext +#undef __init +#undef __initf diff --git a/source/src/main.cpp b/source/src/main.cpp index 8af0093d..f3718e9c 100644 --- a/source/src/main.cpp +++ b/source/src/main.cpp @@ -549,7 +549,7 @@ int main(int argc, char **argv) pushscontext(IEXC_CFG); - #define initlog(s) puts("init: " s) + #define initlog(s) clientlogf("init: " s) initing = INIT_RESET; for(int i = 1; ixs)/image->ys + FONTH/2 + mitemmanual::width(); + if(image && *text != '\t') return (FONTH*image->xs)/image->ys + FONTH/2 + mitemmanual::width(); + return mitemmanual::width(); } virtual void render(int x, int y, int w) { mitem::render(x, y, w); - if(!image) image = filename ? textureload(filename, 3) : notexture; - glBindTexture(GL_TEXTURE_2D, image->id); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glColor3f(1, 1, 1); - int xs = (FONTH*image->xs)/image->ys; - glBegin(GL_QUADS); - glTexCoord2f(0, 0); glVertex2f(x, y); - glTexCoord2f(1, 0); glVertex2f(x+xs, y); - glTexCoord2f(1, 1); glVertex2f(x+xs, y+FONTH); - glTexCoord2f(0, 1); glVertex2f(x, y+FONTH); - glEnd(); - draw_text(text, x+xs + FONTH/2, y); - xtraverts += 4; + if(image || altfont) + { + int xs = 0; + if(image) + { + glBindTexture(GL_TEXTURE_2D, image->id); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glColor3f(1, 1, 1); + xs = (FONTH*image->xs)/image->ys; + glBegin(GL_QUADS); + glTexCoord2f(0, 0); glVertex2f(x, y); + glTexCoord2f(1, 0); glVertex2f(x+xs, y); + glTexCoord2f(1, 1); glVertex2f(x+xs, y+FONTH); + glTexCoord2f(0, 1); glVertex2f(x, y+FONTH); + glEnd(); + xtraverts += 4; + } + draw_text(text, !image || *text == '\t' ? x : x+xs + FONTH/2, y); + if(altfont && strchr(text, '\a')) + { + char *r = newstring(text), *re, *l = r; + while((re = strchr(l, '\a')) && re[1]) + { + *re = '\0'; + x += text_width(l); + l = re + 2; + pushfont(altfont->name); + draw_textf("%c", x, y, re[1]); + popfont(); + } + delete[] r; + } + if(image && isselection() && !hidebigmenuimages && image->ys > FONTH) + { + w += FONTH; + int xs = (2 * VIRTW - w) / 5, ys = (xs * image->ys) / image->xs; + x = (6 * VIRTW + w - 2 * xs) / 4; y = VIRTH - ys / 2; + blendbox(x - FONTH, y - FONTH, x + xs + FONTH, y + ys + FONTH, false); + glBindTexture(GL_TEXTURE_2D, image->id); // I just copy&pasted this... + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // could pls someone with actual OpenGL-knowledge check this? + glColor3f(1, 1, 1); + glBegin(GL_QUADS); + glTexCoord2f(0, 0); glVertex2f(x, y); + glTexCoord2f(1, 0); glVertex2f(x+xs, y); + glTexCoord2f(1, 1); glVertex2f(x+xs, y+ys); + glTexCoord2f(0, 1); glVertex2f(x, y+ys); + glEnd(); + xtraverts += 4; + } + } + else mitemmanual::render(x, y, w); } +}; + +struct mitemimage : mitemimagemanual +{ + mitemimage(gmenu *parent, const char *filename, char *text, char *action, char *hoveraction, color *bgcolor, const char *desc = NULL) : mitemimagemanual(parent, filename, NULL, text, action, hoveraction, bgcolor, desc) {} virtual ~mitemimage() { DELETEA(filename); @@ -593,6 +644,12 @@ void menumanual(void *menu, char *text, char *action, color *bgcolor, const char m.items.add(new mitemmanual(&m, text, action, NULL, bgcolor, desc)); } +void menuimagemanual(void *menu, const char *filename, const char *altfontname, char *text, char *action, color *bgcolor, const char *desc) +{ + gmenu &m = *(gmenu *)menu; + m.items.add(new mitemimagemanual(&m, filename, altfontname, text, action, NULL, bgcolor, desc)); +} + void menutitle(void *menu, const char *title) { gmenu &m = *(gmenu *)menu; @@ -642,7 +699,10 @@ void menuitemvar(char *eval, char *action, char *hoveraction) void menuitemimage(char *name, char *text, char *action, char *hoveraction) { if(!lastmenu) return; - lastmenu->items.add(new mitemimage(lastmenu, newstring(name), newstring(text), action[0] ? newstring(action) : NULL, hoveraction[0] ? newstring(hoveraction) : NULL, NULL)); + if(fileexists(name, "r") || findfile(name, "r") != name) + lastmenu->items.add(new mitemimage(lastmenu, newstring(name), newstring(text), action[0] ? newstring(action) : NULL, hoveraction[0] ? newstring(hoveraction) : NULL, NULL)); + else + lastmenu->items.add(new mitemtext(lastmenu, newstring(text), newstring(action[0] ? action : text), hoveraction[0] ? newstring(hoveraction) : NULL, NULL)); } void menuitemtextinput(char *text, char *value, char *action, char *hoveraction, char *maxchars) @@ -952,6 +1012,7 @@ void gmenu::init() char *f = files[i]; if(!f || !f[0]) continue; char *d = getfiledesc(dirlist->dir, f, dirlist->ext); + s_sprintfd(jpgname)("%s/preview/%s.jpg", dirlist->dir, f); if(dirlist->image) { string fullname = ""; @@ -964,6 +1025,10 @@ void gmenu::init() } items.add(new mitemimage(this, newstring(fullname), f, newstring(dirlist->action), NULL, NULL, d)); } + else if(!strcmp(dirlist->ext, "cgz") && (fileexists(jpgname, "r") || findfile(jpgname, "r") != jpgname)) + { + items.add(new mitemimage(this, newstring(jpgname), f, newstring(dirlist->action), NULL, NULL, d)); + } else items.add(new mitemtext(this, f, newstring(dirlist->action), NULL, NULL, d)); } } diff --git a/source/src/physics.cpp b/source/src/physics.cpp index 55aa5c4b..bf06f793 100644 --- a/source/src/physics.cpp +++ b/source/src/physics.cpp @@ -114,7 +114,7 @@ bool mmcollide(physent *d, float &hi, float &lo) // collide with a map loopv(ents) { entity &e = ents[i]; - if(e.type==CLIP) + if(e.type==CLIP || (e.type == PLCLIP && d->type == ENT_PLAYER)) { if(fabs(e.x-d->o.x) < e.attr2 + d->radius && fabs(e.y-d->o.y) < e.attr3 + d->radius) { @@ -434,10 +434,10 @@ void moveplayer(physent *pl, int moveres, bool local, int curtime) pl->vel.mul(1.25f); } if(water) // dampen velocity change even harder, gives correct water feel - { - pl->vel.x /= 8.0f; - pl->vel.y /= 8.0f; - } + { + pl->vel.x /= 8.0f; + pl->vel.y /= 8.0f; + } else if(pl==player1 || pl->type!=ENT_PLAYER) audiomgr.playsoundc(S_JUMP, pl); pl->lastjump = lastmillis; } diff --git a/source/src/protocol.cpp b/source/src/protocol.cpp index 9ca1d43e..a74dea07 100644 --- a/source/src/protocol.cpp +++ b/source/src/protocol.cpp @@ -92,6 +92,82 @@ void getstring(char *text, ucharbuf &p, int len) DEBUGVAR(text); } +#define follower(x) (((x) & 0xc0) == 0x80) + +int getutf8char(const unsigned char *&s) +{ + int res = *s++; + if(res < 0x80) return res; + if((res & 0xe0) == 0xc0) + { // 2-Byte + if(follower(s[0])) + { + res &= 0x1f; + res <<= 6; + res |= *s++ & 0x3f; + return res; + } + return -1; + } + if((res & 0xf0) == 0xe0) + { // 3-Byte + if(follower(s[0]) && follower(s[1])) + { + res &= 0x0f; + loopi(2) + { + res <<= 6; + res |= *s++ & 0x3f; + } + return res; + } + return -1; + } + if((res & 0xf8) == 0xf0) + { // 4-Byte + if(follower(s[0]) && follower(s[1]) && follower(s[2])) + { + res &= 0x07; + loopi(3) + { + res <<= 6; + res |= *s++ & 0x3f; + } + return res; + } + return -1; + } + return -1; +} + +int pututf8char(unsigned char *&d, int s) +{ + if(s < 0 || s > 0x1fffff) return 0; + if(s < 0x80) + { + *d++ = s; + return 1; + } + if(s < 0x800) + { + *d++ = ((s >> 6) & 0x1f) | 0xc0; + *d++ = (s & 0x3f) | 0x80; + return 2; + } + if(s < 0x10000) + { + *d++ = ((s >> 12) & 0x0f) | 0xe0; + *d++ = ((s >> 6) & 0x3f) | 0x80; + *d++ = (s & 0x3f) | 0x80; + return 3; + } + *d++ = ((s >> 18) & 0x07) | 0xf0; + *d++ = ((s >> 12) & 0x3f) | 0x80; + *d++ = ((s >> 6) & 0x3f) | 0x80; + *d++ = (s & 0x3f) | 0x80; + return 4; +} + void filtertext(char *dst, const char *src, int whitespace, int len) { // whitespace: no whitespace at all (0), blanks only (1), blanks & newline (2) for(int c = *src; c; c = *++src) @@ -99,7 +175,7 @@ void filtertext(char *dst, const char *src, int whitespace, int len) c &= 0x7F; // 7-bit ascii switch(c) { - case '\f': ++src; continue; + case '\f': ++src; continue; } if(isspace(c) ? whitespace && (whitespace>1 || c == ' ') : isprint(c)) { @@ -197,7 +273,7 @@ const char *modeacronymnames[] = }; const char *voteerrors[] = { "voting is currently disabled", "there is already a vote pending", "already voted", "can't vote that often", "this vote is not allowed in the current environment (singleplayer/multiplayer)", "no permission", "invalid vote" }; -const char *mmfullnames[] = { "open", "private" }; +const char *mmfullnames[] = { "open", "private", "match" }; const char *fullmodestr(int n) { return (n>=-1 && size_t(n+1) < sizeof(modefullnames)/sizeof(modefullnames[0])) ? modefullnames[n+1] : "unknown"; } const char *acronymmodestr(int n) { return (n>=-1 && size_t(n+1) < sizeof(modeacronymnames)/sizeof(modeacronymnames[0])) ? modeacronymnames[n+1] : "n/a"; } @@ -207,27 +283,28 @@ const char *mmfullname(int n) { return (n>=0 && n < MM_NUM) ? mmfullnames[n] : " char msgsizesl[] = // size inclusive message token, 0 for variable or not-checked sizes { - SV_INITS2C, 5, SV_WELCOME, 2, SV_INITC2S, 0, SV_POS, 0, SV_TEXT, 0, SV_TEAMTEXT, 0, SV_SOUND, 2, SV_VOICECOM, 2, SV_VOICECOMTEAM, 2, SV_CDIS, 2, + SV_INITS2C, 5, SV_WELCOME, 2, SV_INITC2S, 0, SV_POS, 0, SV_TEXT, 0, SV_TEAMTEXT, 0, SV_TEXTME, 0, SV_TEAMTEXTME, 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_SCOPE, 3, SV_GIBDIED, 4, SV_DIED, 4, SV_GIBDAMAGE, 6, SV_DAMAGE, 6, SV_HITPUSH, 6, SV_SHOTFX, 9, SV_THROWNADE, 8, - SV_TRYSPAWN, 1, SV_SPAWNSTATE, 23, SV_SPAWN, 3, SV_FORCEDEATH, 2, SV_RESUME, 0, - SV_TIMEUP, 2, SV_EDITENT, 10, SV_ITEMACC, 2, + SV_TRYSPAWN, 1, SV_SPAWNSTATE, 23, SV_SPAWN, 3, SV_SPAWNDENY, 2, SV_FORCEDEATH, 2, SV_RESUME, 0, + SV_DISCSCORES, 0, SV_TIMEUP, 2, SV_EDITENT, 10, 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_EDITMODE, 2, SV_EDITH, 7, SV_EDITT, 7, SV_EDITS, 6, 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_FLAGACTION, 3, SV_FLAGINFO, 0, SV_FLAGMSG, 0, SV_FLAGCNT, 3, - SV_ARENAWIN, 2, + SV_ARENAWIN, 2, SV_LMSITEM, 3, SV_SETADMIN, 0, SV_SERVOPINFO, 3, SV_CALLVOTE, 0, SV_CALLVOTESUC, 1, SV_CALLVOTEERR, 2, SV_VOTE, 2, SV_VOTERESULT, 2, - SV_FORCETEAM, 3, SV_AUTOTEAM, 2, SV_CHANGETEAM, 1, + SV_TRYTEAM, 2, SV_SETTEAM, 3, SV_TEAMDENY, 2, SV_SERVERMODE, 2, SV_WHOIS, 2, SV_WHOISINFO, 3, SV_LISTDEMOS, 1, SV_SENDDEMOLIST, 0, SV_GETDEMO, 2, SV_SENDDEMO, 0, SV_DEMOPLAYBACK, 3, - SV_CONNECT, 0, + SV_CONNECT, 0, SV_SPECTCN, 2, SV_CLIENT, 0, SV_EXTENSION, 0, - SV_MAPIDENT, 3, SV_FORCENOTIFY, 3, + SV_MAPIDENT, 3, -1 }; diff --git a/source/src/protocol.h b/source/src/protocol.h index 24c8a58b..a5caf06c 100644 --- a/source/src/protocol.h +++ b/source/src/protocol.h @@ -6,8 +6,9 @@ #define CUBE_SERVINFO_PORT(serverport) (serverport+1) #define CUBE_SERVINFO_TO_SERV_PORT(servinfoport) (servinfoport-1) #define PROTOCOL_VERSION 1129 // bump when protocol changes (use negative numbers for mods!) -#define DEMO_VERSION 1 // bump when demo format changes +#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 MAXMAPSENDSIZE 65536 #define MAXCFGFILESIZE 65536 @@ -17,27 +18,28 @@ extern bool modprotocol; // network messages codes, c2s, c2c, s2c enum { - SV_INITS2C = 0, SV_WELCOME, SV_INITC2S, SV_POS, SV_TEXT, SV_TEAMTEXT, SV_SOUND, SV_VOICECOM, SV_VOICECOMTEAM, SV_CDIS, - SV_SHOOT, SV_EXPLODE, SV_SUICIDE, SV_AKIMBO, SV_RELOAD, SV_SCOPE, + SV_INITS2C = 0, SV_WELCOME, SV_INITC2S, SV_POS, SV_TEXT, SV_TEAMTEXT, SV_TEXTME, SV_TEAMTEXTME, //7 + SV_SOUND, SV_VOICECOM, SV_VOICECOMTEAM, SV_CDIS, + SV_SHOOT, SV_EXPLODE, SV_SUICIDE, SV_AKIMBO, SV_RELOAD, SV_SCOPE, //17 SV_GIBDIED, SV_DIED, SV_GIBDAMAGE, SV_DAMAGE, SV_HITPUSH, SV_SHOTFX, SV_THROWNADE, - SV_TRYSPAWN, SV_SPAWNSTATE, SV_SPAWN, SV_FORCEDEATH, SV_RESUME, - SV_TIMEUP, SV_EDITENT, SV_ITEMACC, + 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_EDITMODE, SV_EDITH, SV_EDITT, SV_EDITS, SV_EDITD, SV_EDITE, SV_NEWMAP, SV_SENDMAP, SV_RECVMAP, SV_REMOVEMAP, SV_SERVMSG, SV_ITEMLIST, SV_WEAPCHANGE, SV_PRIMARYWEAP, SV_FLAGACTION, SV_FLAGINFO, SV_FLAGMSG, SV_FLAGCNT, - SV_ARENAWIN, + SV_ARENAWIN, SV_LMSITEM, SV_SETADMIN, SV_SERVOPINFO, SV_CALLVOTE, SV_CALLVOTESUC, SV_CALLVOTEERR, SV_VOTE, SV_VOTERESULT, - SV_FORCETEAM, SV_AUTOTEAM, SV_CHANGETEAM, + SV_TRYTEAM, SV_SETTEAM, SV_TEAMDENY, SV_SERVERMODE, SV_WHOIS, SV_WHOISINFO, SV_LISTDEMOS, SV_SENDDEMOLIST, SV_GETDEMO, SV_SENDDEMO, SV_DEMOPLAYBACK, - SV_CONNECT, + SV_CONNECT, SV_SPECTCN, SV_CLIENT, SV_EXTENSION, - SV_MAPIDENT, SV_FORCENOTIFY, SV_NUM + SV_MAPIDENT, SV_NUM }; #ifdef _DEBUG @@ -47,37 +49,41 @@ extern void protocoldebug(bool enable); // converts message code to char const static char *messagenames[] = { - "SV_INITS2C", "SV_WELCOME", "SV_INITC2S", "SV_POS", "SV_TEXT", "SV_TEAMTEXT", "SV_SOUND", "SV_VOICECOM", "SV_VOICECOMTEAM", "SV_CDIS", + "SV_INITS2C", "SV_WELCOME", "SV_INITC2S", "SV_POS", "SV_TEXT", "SV_TEAMTEXT", "SV_TEXTME", "SV_TEAMTEXTME", + "SV_SOUND", "SV_VOICECOM", "SV_VOICECOMTEAM", "SV_CDIS", "SV_SHOOT", "SV_EXPLODE", "SV_SUICIDE", "SV_AKIMBO", "SV_RELOAD", "SV_SCOPE", "SV_GIBDIED", "SV_DIED", "SV_GIBDAMAGE", "SV_DAMAGE", "SV_HITPUSH", "SV_SHOTFX", "SV_THROWNADE", - "SV_TRYSPAWN", "SV_SPAWNSTATE", "SV_SPAWN", "SV_FORCEDEATH", "SV_RESUME", - "SV_TIMEUP", "SV_EDITENT", "SV_ITEMACC", + "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_EDITMODE", "SV_EDITH", "SV_EDITT", "SV_EDITS", "SV_EDITD", "SV_EDITE", "SV_NEWMAP", "SV_SENDMAP", "SV_RECVMAP", "SV_REMOVEMAP", "SV_SERVMSG", "SV_ITEMLIST", "SV_WEAPCHANGE", "SV_PRIMARYWEAP", "SV_FLAGACTION", "SV_FLAGINFO", "SV_FLAGMSG", "SV_FLAGCNT", - "SV_ARENAWIN", + "SV_ARENAWIN", "SV_LMSITEM", "SV_SETADMIN", "SV_SERVOPINFO", "SV_CALLVOTE", "SV_CALLVOTESUC", "SV_CALLVOTEERR", "SV_VOTE", "SV_VOTERESULT", - "SV_FORCETEAM", "SV_AUTOTEAM", "SV_CHANGETEAM", + "SV_TRYTEAM", "SV_SETTEAM", "SV_TEAMDENY", "SV_SERVERMODE", "SV_WHOIS", "SV_WHOISINFO", "SV_LISTDEMOS", "SV_SENDDEMOLIST", "SV_GETDEMO", "SV_SENDDEMO", "SV_DEMOPLAYBACK", - "SV_CONNECT", + "SV_CONNECT", "SV_SPECTCN", "SV_CLIENT", "SV_EXTENSION", - "SV_MAPIDENT", "SV_FORCENOTIFY" + "SV_MAPIDENT" }; #endif enum { SA_KICK = 0, SA_BAN, SA_REMBANS, SA_MASTERMODE, SA_AUTOTEAM, SA_FORCETEAM, SA_GIVEADMIN, SA_MAP, SA_RECORDDEMO, SA_STOPDEMO, SA_CLEARDEMOS, SA_SERVERDESC, SA_SHUFFLETEAMS, SA_NUM}; enum { VOTE_NEUTRAL = 0, VOTE_YES, VOTE_NO, VOTE_NUM }; enum { VOTEE_DISABLED = 0, VOTEE_CUR, VOTEE_MUL, VOTEE_MAX, VOTEE_AREA, VOTEE_PERMISSION, VOTEE_INVALID, VOTEE_NUM }; -enum { MM_OPEN = 0, MM_PRIVATE, MM_MATCH, MM_NUM }; -enum { AT_DISABLED = 0, AT_ENABLED, AT_SHUFFLE, AT_NUM }; +enum { MM_OPEN = 0, MM_PRIVATE, MM_MATCH, MM_NUM }; enum { MM_MASK = 0x03 }; +enum { AT_DISABLED = 0, AT_ENABLED = 1, AT_SHUFFLE = 2, AT_NUM }; enum { FA_PICKUP = 0, FA_STEAL, FA_DROP, FA_LOST, FA_RETURN, FA_SCORE, FA_NUM, FA_RESET }; enum { FM_PICKUP = 0, FM_DROP, FM_LOST, FM_RETURN, FM_SCORE, FM_KTFSCORE, FM_SCOREFAIL, FM_RESET, FM_NUM }; +enum { LMSITEM_CLEAR = 0, LMSITEM_ANNOUNCE, LMSITEM_SPAWN, LMSITEM_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 ( playernames; uchar namedata[MAXTRANS]; @@ -206,11 +209,15 @@ struct serverinfo color *bgcolor; int favcat, msweight, weight; + int uplinkqual, uplinkqual_age; + unsigned char uplinkstats[MAXCLIENTS + 1]; + serverinfo() - : mode(0), numplayers(0), maxclients(0), ping(9999), protocol(0), minremain(0), resolved(UNRESOLVED), port(-1), lastpingmillis(0), pongflags(0), getnames(0), getinfo(0), bgcolor(NULL), favcat(-1), msweight(0), weight(0) + : mode(0), numplayers(0), maxclients(0), ping(9999), protocol(0), minremain(0), resolved(UNRESOLVED), port(-1), lastpingmillis(0), pongflags(0), getinfo(EXTPING_NOP), bgcolor(NULL), favcat(-1), msweight(0), weight(0), uplinkqual(0), uplinkqual_age(0) { name[0] = full[0] = map[0] = sdesc[0] = description[0] = '\0'; loopi(3) lang[i] = '\0'; + loopi(MAXCLIENTS + 1) uplinkstats[i] = 0; } }; @@ -347,9 +354,10 @@ extern void listdemos(); extern flaginfo flaginfos[2]; extern int sessionid; extern bool watchingdemo; -extern bool autoteambalance; +struct serverstate { int autoteam; int mastermode; int matchteamsize; void reset() { autoteam = mastermode = matchteamsize = 0; }}; +extern struct serverstate servstate; extern void updateworld(int curtime, int lastmillis); -extern void resetmap(); +extern void resetmap(bool mrproper = true); extern void startmap(const char *name, bool reset = true); extern void changemap(const char *name); extern void initclient(); @@ -367,7 +375,7 @@ extern playerent *getclient(int cn); extern playerent *newclient(int cn); extern void timeupdate(int timeremain); extern void respawnself(); -extern void setskin(playerent *pl, uint skin); +extern void setskin(playerent *pl, int skin, int team = -1); extern void callvote(int type, char *arg1 = NULL, char *arg2 = NULL); extern void addsleep(int msec, const char *cmd); extern void resetsleep(); @@ -389,6 +397,7 @@ extern void refreshsopmenu(void *menu, bool init); extern char *colorname(playerent *d, char *name = NULL, const char *prefix = ""); extern char *colorping(int ping); extern char *colorpj(int pj); +extern const char *highlight(const char *text); extern void togglespect(); extern playerent *updatefollowplayer(int shiftdirection = 0); extern void spectate(int mode); @@ -411,6 +420,8 @@ extern void votecount(int v); extern void clearvote(); // scoreboard +struct discscore { int team, flags, frags, deaths; char name[MAXNAMELEN + 1]; }; +extern vector discscores; extern void showscores(int on); extern void renderscores(void *menu, bool init); extern const char *asciiscores(bool destjpg = false); @@ -418,6 +429,7 @@ extern void consolescores(); // world extern void setupworld(int factor); +extern bool worldbordercheck(int x1, int x2, int y1, int y2, int z1, int z2); extern bool empty_world(int factor, bool force); extern void remip(const block &b, int level = 0); extern void remipmore(const block &b, int level = 0); @@ -494,6 +506,7 @@ extern int VIRTW; // virtual screen size for text & HUD extern font *curfont; extern bool setfont(const char *name); +extern font *getfont(const char *name); extern void pushfont(const char *name); extern void popfont(); extern void draw_text(const char *str, int left, int top, int r = 255, int g = 255, int b = 255, int a = 255, int cursor = -1, int maxwidth = -1); @@ -646,13 +659,11 @@ extern void checkakimbo(); extern struct projectile *newprojectile(vec &from, vec &to, float speed, bool local, playerent *owner, int gun, int id = lastmillis); // entities -extern const char *entnames[]; - extern void spawnallitems(); extern void pickupeffects(int n, playerent *d); extern void renderentities(); extern void rendermapmodels(); -extern void resetspawns(); +extern void resetspawns(int type = -1); extern void setspawn(int i, bool on); extern void checkitems(playerent *d); @@ -709,11 +720,13 @@ extern int wizardmain(int argc, char **argv); // demo #define DHDR_DESCCHARS 80 +#define DHDR_PLISTCHARS 322 struct demoheader { char magic[16]; int version, protocol; char desc[DHDR_DESCCHARS]; + char plist[DHDR_PLISTCHARS]; }; // logging @@ -745,7 +758,7 @@ struct servercommandline { int uprate, serverport, syslogfacility, filethres, syslogthres, maxdemos, maxclients, kickthreshold, banthreshold, verbose; const char *ip, *master, *logident, *serverpassword, *adminpasswd, *demopath, *maprot, *pwdfile, *blfile, *nbfile, *infopath, *motdpath; - bool demoeverymatch, logtimestamp; + bool logtimestamp; string motd, servdesc_full, servdesc_pre, servdesc_suf, voteperm, mapperm; int clfilenesting; vector adminonlymaps; @@ -755,7 +768,7 @@ struct servercommandline 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"), - demoeverymatch(true), logtimestamp(false), + logtimestamp(false), clfilenesting(0) { motd[0] = servdesc_full[0] = servdesc_pre[0] = servdesc_suf[0] = voteperm[0] = mapperm[0] = '\0'; @@ -788,13 +801,7 @@ struct servercommandline case 'y': if(ai < 0) banthreshold = ai; break; case 'x': adminpasswd = a; break; case 'p': serverpassword = a; break; - case 'D': - if(isdigit(*a)) - { - maxdemos = ai; - demoeverymatch = maxdemos > 0; - } - break; + case 'D': if(ai > 0) maxdemos = ai; break; case 'W': demopath = a; break; case 'r': maprot = a; break; case 'X': pwdfile = a; break; diff --git a/source/src/renderhud.cpp b/source/src/renderhud.cpp index 72125b62..edd543c1 100644 --- a/source/src/renderhud.cpp +++ b/source/src/renderhud.cpp @@ -645,7 +645,11 @@ void gl_drawhud(int w, int h, int curfps, int nquads, int curvert, bool underwat { glLoadIdentity(); glOrtho(0, VIRTW, VIRTH, 0, -1, 1); - draw_text("SPECTATING", VIRTW/40, VIRTH/10*7); + const char *specttext = "SPECTATING"; + if(player1->team == TEAM_SPECT) specttext = "SPECTATOR"; + else if(player1->team == TEAM_CLA_SPECT) specttext = "SPECTATOR(CLA)"; + else if(player1->team == TEAM_RVSF_SPECT) specttext = "SPECTATOR(RVSF)"; + draw_text(specttext, VIRTW/40, VIRTH/10*7); if(player1->spectatemode==SM_FOLLOW1ST || player1->spectatemode==SM_FOLLOW3RD || player1->spectatemode==SM_FOLLOW3RD_TRANSPARENT) { if(players.inrange(player1->followplayercn) && players[player1->followplayercn]) diff --git a/source/src/rendermodel.cpp b/source/src/rendermodel.cpp index d78c6d77..10212145 100644 --- a/source/src/rendermodel.cpp +++ b/source/src/rendermodel.cpp @@ -694,7 +694,7 @@ void renderclient(playerent *d) if(!d) return; int team = team_base(d->team); const char *cs = NULL, *skinbase = SKINBASE, *teamname = team_basestring(team); - int skinid = 1 + max(0, min(d->skin, (team==TEAM_CLA ? 3 : 5))); + int skinid = 1 + d->skin(); string skin; if(hidecustomskins == 0 || (hidecustomskins == 1 && !m_teammode)) { diff --git a/source/src/rendertext.cpp b/source/src/rendertext.cpp index 53cf69c8..6ef31461 100644 --- a/source/src/rendertext.cpp +++ b/source/src/rendertext.cpp @@ -63,6 +63,11 @@ bool setfont(const char *name) return true; } +font *getfont(const char *name) +{ + return fonts.access(name); +} + static vector fontstack; void pushfont(const char *name) @@ -75,8 +80,8 @@ void popfont() { if(!fontstack.empty()) curfont = fontstack.pop(); } - -int text_width(const char *str) + +int text_width(const char *str) { int width, height; text_bounds(str, width, height); @@ -107,18 +112,19 @@ static int draw_char(int c, int x, int y) } //stack[sp] is current color index -static void text_color(char c, char *stack, int size, int &sp, bvec color, int a) +static void text_color(char c, char *stack, int size, int &sp, bvec color, int a) { if(c=='s') // save color - { + { c = stack[sp]; if(sp 0) ? --sp : sp]; // restore color + else if(c == 'b') stack[sp] *= -1; else stack[sp] = c; - switch(c) + switch(abs(stack[sp])) { case '0': color = bvec(64, 255, 128); break; // green: player talk case '1': color = bvec(96, 160, 255); break; // blue: team chat @@ -130,8 +136,10 @@ static void text_color(char c, char *stack, int size, int &sp, bvec color, int a case '7': color = bvec(128, 48, 48); break; // dark red: dead admin // white (provided color): everything else } - glColor4ub(color.x, color.y, color.z, a); - } + int b = (int) (sinf(lastmillis / 200.0f) * 115.0f); + b = stack[sp] > 0 ? 100 : min(abs(b), 100); + glColor4ub(color.x, color.y, color.z, (a * b) / 100); + } } static vector *columns = NULL; @@ -181,6 +189,7 @@ void text_endcolumns() else if(c==' ') { x += curfont->defaultw; TEXTWHITE(i) }\ else if(c=='\n') { TEXTLINE(i) x = 0; y += FONTH; }\ else if(c=='\f') { if(str[i+1]) { i++; TEXTCOLOR(i) }}\ + else if(c=='\a') { if(str[i+1]) { i++; }}\ else if(curfont->chars.inrange(c-curfont->skip))\ {\ if(maxwidth != -1)\ @@ -236,7 +245,7 @@ int text_visible(const char *str, int hitx, int hity, int maxwidth) } //inverse of text_visible -void text_pos(const char *str, int cursor, int &cx, int &cy, int maxwidth) +void text_pos(const char *str, int cursor, int &cx, int &cy, int maxwidth) { #define TEXTINDEX(idx) if(idx == cursor) { cx = x; cy = y; break; } #define TEXTTAB(idx) TEXTGETCOLUMN @@ -280,12 +289,12 @@ void text_bounds(const char *str, int &width, int &height, int maxwidth) #undef TEXTWORD } -void draw_text(const char *str, int left, int top, int r, int g, int b, int a, int cursor, int maxwidth) +void draw_text(const char *str, int left, int top, int r, int g, int b, int a, int cursor, int maxwidth) { #define TEXTINDEX(idx) if(idx == cursor) { cx = x; cy = y; cc = str[idx]; } #define TEXTTAB(idx) TEXTGETCOLUMN #define TEXTWHITE(idx) - #define TEXTLINE(idx) + #define TEXTLINE(idx) #define TEXTCOLOR(idx) text_color(str[idx], colorstack, sizeof(colorstack), colorpos, color, a); #define TEXTCHAR(idx) x += draw_char(c, left+x, top+y)+1; #define TEXTWORD TEXTWORDSKELETON diff --git a/source/src/scoreboard.cpp b/source/src/scoreboard.cpp index c6ce857e..fc6bdeaf 100644 --- a/source/src/scoreboard.cpp +++ b/source/src/scoreboard.cpp @@ -26,6 +26,7 @@ struct sline }; static vector scorelines; +vector discscores; struct teamscore { @@ -33,7 +34,7 @@ struct teamscore vector teammembers; teamscore(int t) : team(t), frags(0), deaths(0), flagscore(0) {} - void addscore(playerent *d) + void addplayer(playerent *d) { if(!d) return; teammembers.add(d); @@ -41,6 +42,13 @@ struct teamscore deaths += d->deaths; if(m_flags) flagscore += d->flagscore; } + + void addscore(discscore &d) + { + frags += d.frags; + deaths += d.deaths; + if(m_flags) flagscore += d.flags; + } }; static int teamscorecmp(const teamscore *x, const teamscore *y) @@ -50,8 +58,7 @@ static int teamscorecmp(const teamscore *x, const teamscore *y) if(x->frags > y->frags) return -1; if(x->frags < y->frags) return 1; if(x->deaths < y->deaths) return -1; - if(x->deaths > y->deaths) return 1; - return x->team > y->team; + return 1; } static int scorecmp(const playerent **x, const playerent **y) @@ -67,43 +74,63 @@ static int scorecmp(const playerent **x, const playerent **y) return strcmp((*x)->name, (*y)->name); } -struct scoreratio +static int discscorecmp(const discscore *x, const discscore *y) { - float ratio; - int precision; + if(x->team < y->team) return -1; + if(x->team > y->team) return 1; + if(m_flags && x->flags > y->flags) return -1; + if(m_flags && x->flags < y->flags) return 1; + if(x->frags > y->frags) return -1; + if(x->frags < y->frags) return 1; + if(x->deaths > y->deaths) return 1; + if(x->deaths < y->deaths) return -1; + return strcmp(x->name, y->name); +} - void calc(int frags, int deaths) +const char *scoreratio(int frags, int deaths) +{ + static string res; + float ratio = (float)(frags >= 0 ? frags : 0) / (float)(deaths > 0 ? deaths : 1); + int precision = 0; + if(ratio<10.0f) precision = 2; + else if(ratio>=10.0f && ratio<100.0f) precision = 1; + s_sprintf(res)("%.*f", precision, ratio); + return res; +} + +void renderdiscscores(int team) +{ + loopv(discscores) if(team == team_group(discscores[i].team)) { - // ratio - if(frags>=0 && deaths>0) ratio = (float)frags/(float)deaths; - else if(frags>=0 && deaths==0) ratio = frags; - else ratio = 0.0f; - - // precision - if(ratio<10.0f) precision = 2; - else if(ratio>=10.0f && ratio<100.0f) precision = 1; - else precision = 0; + discscore &d = discscores[i]; + sline &line = scorelines.add(); + const char *spect = team_isspect(d.team) ? "\f4" : ""; + const char *sr = scoreratio(d.frags, d.deaths); + const char *clag = team_isspect(d.team) ? "SPECT" : ""; + if(m_flags) s_sprintf(line.s)("%s%d\t%d\t%d\t%s\t%s\tDISC\t\t%s", spect, d.flags, d.frags, d.deaths, sr, clag, d.name); + else s_sprintf(line.s)("%s%d\t%d\t%s\t%s\tDISC\t\t%s", spect, d.frags, d.deaths, sr, clag, d.name); } -}; +} -void renderscore(void *menu, playerent *d) +void renderscore(playerent *d) { const char *status = ""; static color localplayerc(0.2f, 0.2f, 0.2f, 0.2f); if(d->clientrole==CR_ADMIN) status = d->state==CS_DEAD ? "\f7" : "\f3"; else if(d->state==CS_DEAD) status = "\f4"; - const char *clag = d->state==CS_LAGGED ? "LAG" : colorpj(d->plag), *cping = colorping(d->ping); + const char *spect = team_isspect(d->team) ? "\f4" : ""; + const char *sr = scoreratio(d->frags, d->deaths); + const char *clag = team_isspect(d->team) ? "SPECT" : (d->state==CS_LAGGED ? "LAG" : colorpj(d->plag)); + const char *cping = colorping(d->ping); + const char *ign = d->ignored ? " (ignored)" : (d->muted ? " (muted)" : ""); sline &line = scorelines.add(); line.bgcolor = d==player1 ? &localplayerc : NULL; string &s = line.s; - scoreratio sr; - sr.calc(d->frags, d->deaths); - if(m_flags) s_sprintf(s)("%d\t%d\t%d\t%.*f\t%s\t%s\t%d\t%s%s", d->flagscore, d->frags, d->deaths, sr.precision, sr.ratio, clag, cping, d->clientnum, status, colorname(d)); - else if(m_teammode) s_sprintf(s)("%d\t%d\t%.*f\t%s\t%s\t%d\t%s%s", d->frags, d->deaths, sr.precision, sr.ratio, clag, cping, d->clientnum, status, colorname(d)); - else s_sprintf(s)("%d\t%d\t%.*f\t%s\t%s\t%d\t%s%s", d->frags, d->deaths, sr.precision, sr.ratio, clag, cping, d->clientnum, status, colorname(d)); + if(m_flags) s_sprintf(s)("%s%d\t%d\t%d\t%s\t%s\t%s\t%d\t%s%s%s", spect, d->flagscore, d->frags, d->deaths, sr, clag, cping, d->clientnum, status, colorname(d), ign); + else s_sprintf(s)("%s%d\t%d\t%s\t%s\t%s\t%d\t%s%s%s", spect, d->frags, d->deaths, sr, clag, cping, d->clientnum, status, colorname(d), ign); } -void renderteamscore(void *menu, teamscore *t) +void renderteamscore(teamscore *t) { if(!scorelines.empty()) // space between teams { @@ -112,13 +139,12 @@ void renderteamscore(void *menu, teamscore *t) } sline &line = scorelines.add(); s_sprintfd(plrs)("(%d %s)", t->teammembers.length(), t->teammembers.length() == 1 ? "player" : "players"); - scoreratio sr; - sr.calc(t->frags, t->deaths); - if(m_flags) s_sprintf(line.s)("%d\t%d\t%d\t%.*f\t\t\t\t%s\t\t%s", t->flagscore, t->frags, t->deaths, sr.precision, sr.ratio, team_string(t->team), plrs); - else if(m_teammode) s_sprintf(line.s)("%d\t%d\t%.*f\t\t\t\t%s\t\t%s", t->frags, t->deaths, sr.precision, sr.ratio, team_string(t->team), plrs); + const char *sr = scoreratio(t->frags, t->deaths); + if(m_flags) s_sprintf(line.s)("%d\t%d\t%d\t%s\t\t\t\t%s\t\t%s", t->flagscore, t->frags, t->deaths, sr, team_string(t->team), plrs); + else s_sprintf(line.s)("%d\t%d\t%s\t\t\t\t%s\t\t%s", t->frags, t->deaths, sr, team_string(t->team), plrs); static color teamcolors[2] = { color(1.0f, 0, 0, 0.2f), color(0, 0, 1.0f, 0.2f) }; line.bgcolor = &teamcolors[team_base(t->team)]; - loopv(t->teammembers) renderscore(menu, t->teammembers[i]); + loopv(t->teammembers) renderscore(t->teammembers[i]); } extern bool watchingdemo; @@ -135,12 +161,11 @@ void renderscores(void *menu, bool init) if(!watchingdemo) scores.add(player1); loopv(players) if(players[i]) scores.add(players[i]); scores.sort(scorecmp); + discscores.sort(discscorecmp); - if(init) - { - int sel = scores.find(player1); - if(sel>=0) menuselect(menu, sel); - } + int spectators = 0; + loopv(scores) if(scores[i]->team == TEAM_SPECT) spectators++; + loopv(discscores) if(discscores[i].team == TEAM_SPECT) spectators++; if(getclientmap()[0]) { @@ -169,22 +194,39 @@ void renderscores(void *menu, bool init) { teamscore teamscores[2] = { teamscore(TEAM_CLA), teamscore(TEAM_RVSF) }; - loopv(players) + loopv(scores) if(scores[i]->team != TEAM_SPECT) { - if(!players[i]) continue; - teamscores[team_base(players[i]->team)].addscore(players[i]); + teamscores[team_base(scores[i]->team)].addplayer(scores[i]); } - if(!watchingdemo) teamscores[team_base(player1->team)].addscore(player1); - loopi(2) teamscores[i].teammembers.sort(scorecmp); - int sort = teamscorecmp(&teamscores[TEAM_CLA], &teamscores[TEAM_RVSF]); - loopi(2) renderteamscore(menu, &teamscores[sort < 0 ? i : (i+1)&1]); + loopv(discscores) if(discscores[i].team != TEAM_SPECT) + { + teamscores[team_base(discscores[i].team)].addscore(discscores[i]); + } + + int sort = teamscorecmp(&teamscores[TEAM_CLA], &teamscores[TEAM_RVSF]) < 0 ? 0 : 1; + loopi(2) + { + renderteamscore(&teamscores[sort ^ i]); + renderdiscscores(sort ^ i); + } } else - { - loopv(scores) renderscore(menu, scores[i]); - } + { // ffa mode + loopv(scores) if(scores[i]->team != TEAM_SPECT) renderscore(scores[i]); + loopi(2) renderdiscscores(i); + } + if(spectators) + { + if(!scorelines.empty()) // space between teams and spectators + { + sline &space = scorelines.add(); + space.s[0] = 0; + } + renderdiscscores(TEAM_SPECT); + loopv(scores) if(scores[i]->team == TEAM_SPECT) renderscore(scores[i]); + } menureset(menu); loopv(scorelines) menumanual(menu, scorelines[i].s, NULL, scorelines[i].bgcolor); menuheader(menu, modeline, serverline); @@ -207,7 +249,6 @@ const char *asciiscores(bool destjpg) static char *buf = NULL; static string team, flags, text; playerent *d; - scoreratio sr; vector scores; if(!buf) buf = (char *) malloc(MAXJPGCOM +1); @@ -249,16 +290,29 @@ const char *asciiscores(bool destjpg) loopv(scores) { d = scores[i]; - sr.calc(d->frags, d->deaths); + const char *sr = scoreratio(d->frags, d->deaths); s_sprintf(team)(destjpg ? ", %s" : " %-4s", team_string(d->team, true)); s_sprintf(flags)(destjpg ? "%d/" : " %4d ", d->flagscore); if(destjpg) s_sprintf(text)("%s%s (%s%d/%d)\n", d->name, m_teammode ? team : "", m_flags ? flags : "", d->frags, d->deaths); else - s_sprintf(text)("%s %4d %4d %5.2f %2d%s %s%s\n", m_flags ? flags : "", d->frags, d->deaths, sr.ratio, d->clientnum, + s_sprintf(text)("%s %4d %4d %5s %2d%s %s%s\n", m_flags ? flags : "", d->frags, d->deaths, sr, d->clientnum, m_teammode ? team : "", d->name, d->clientrole==CR_ADMIN ? " (admin)" : d==player1 ? " (you)" : ""); addstr(buf, text); } + discscores.sort(discscorecmp); + loopv(discscores) + { + discscore &d = discscores[i]; + const char *sr = scoreratio(d.frags, d.deaths); + s_sprintf(team)(destjpg ? ", %s" : " %-4s", team_string(d.team, true)); + s_sprintf(flags)(destjpg ? "%d/" : " %4d ", d.flags); + if(destjpg) + s_sprintf(text)("%s(disconnected)%s (%s%d/%d)\n", d.name, m_teammode ? team : "", m_flags ? flags : "", d.frags, d.deaths); + else + s_sprintf(text)("%s %4d %4d %5s --%s %s(disconnected)\n", m_flags ? flags : "", d.frags, d.deaths, sr, m_teammode ? team : "", d.name); + addstr(buf, text); + } if(destjpg) { extern int minutesremaining; diff --git a/source/src/server.cpp b/source/src/server.cpp index dc65454d..b318d954 100644 --- a/source/src/server.cpp +++ b/source/src/server.cpp @@ -1,6 +1,7 @@ // server.cpp: little more than enhanced multicaster // runs dedicated or as client coroutine +#define SERVER_CPP #include "pch.h" #ifdef WIN32 @@ -41,6 +42,7 @@ vector demofiles; int mastermode = MM_OPEN; static bool autoteam = true; +int matchteamsize = 0; static bool forceintermission = false; @@ -51,7 +53,7 @@ bool custom_servdesc = false; // current game string smapname, nextmapname; int smode = 0, nextgamemode; -static int interm = 0, minremain = 0, gamemillis = 0, gamelimit = 0; +static int interm = 0, minremain = 0, gamemillis = 0, gamelimit = 0, lmsitemtype = 0; mapstats smapstats; vector sents; char *maplayout = NULL; @@ -213,23 +215,55 @@ int numactiveclients() return num; } -int freeteam(int pl = -1) +int *numteamclients(int exclude = -1) { - int teamsize[2] = {0, 0}; - int teamscore[2] = {0, 0}; - int t; - int sum = calcscores(); - loopv(clients) if(clients[i]->type!=ST_EMPTY && i != pl && clients[i]->isauthed && team_isactive(clients[i]->team)) + static int num[TEAM_NUM]; + loopi(TEAM_NUM) num[i] = 0; + loopv(clients) if(i != exclude && clients[i]->type!=ST_EMPTY && clients[i]->isauthed && clients[i]->isonrightmap && team_isvalid(clients[i]->team)) num[clients[i]->team]++; + return num; +} + +int sendservermode(bool send = true) +{ + int sm = (autoteam ? AT_ENABLED : AT_DISABLED) | ((mastermode & MM_MASK) << 2) | (matchteamsize << 4); + if(send) sendf(-1, 1, "ri2", SV_SERVERMODE, sm); + return sm; +} + +void changematchteamsize(int newteamsize) +{ + if(newteamsize < 0) return; + if(matchteamsize != newteamsize) sendservermode(); + matchteamsize = newteamsize; + if(mastermode == MM_MATCH && matchteamsize && m_teammode) { - t = team_base(clients[i]->team); - teamsize[t]++; - teamscore[t] += clients[i]->at3_score; + 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(teamsize[0] == teamsize[1]) +} + +void changemastermode(int newmode) +{ + if(mastermode != newmode) { - return sum > 200 ? (teamscore[0] < teamscore[1] ? 0 : 1) : rnd(2); + mastermode = newmode; + senddisconnectedscores(-1); + if(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) updateclientteam(i, TEAM_SPECT, FTR_SILENTFORCE); + } + } + else if(matchteamsize) changematchteamsize(matchteamsize); } - return teamsize[0] < teamsize[1] ? 0 : 1; + sendservermode(); } int findcnbyaddress(ENetAddress *address) @@ -245,6 +279,7 @@ int findcnbyaddress(ENetAddress *address) savedscore *findscore(client &c, bool insert) { if(c.type!=ST_TCPIP) return NULL; + enet_uint32 mask = htonl(mastermode == MM_MATCH ? 0xFFFF0000 : 0xFFFFFFFF); // in match mode, reconnecting from /16 subnet is allowed if(!insert) { loopv(clients) @@ -254,7 +289,7 @@ savedscore *findscore(client &c, bool insert) if(o.clientnum!=c.clientnum && o.peer->address.host==c.peer->address.host && !strcmp(o.name, c.name)) { static savedscore curscore; - curscore.save(o.state); + curscore.save(o.state, o.team); return &curscore; } } @@ -262,7 +297,7 @@ savedscore *findscore(client &c, bool insert) loopv(savedscores) { savedscore &sc = savedscores[i]; - if(!strcmp(sc.name, c.name) && sc.ip==c.peer->address.host) return ≻ + if(!strcmp(sc.name, c.name) && (sc.ip & mask) == (c.peer->address.host & mask)) return ≻ } if(!insert) return NULL; savedscore &sc = savedscores.add(); @@ -331,17 +366,13 @@ void sendservmsg(const char *msg, int client=-1) sendf(client, 1, "ris", SV_SERVMSG, msg); } -void spawnstate(client *c) -{ - clientstate &gs = c->state; - gs.spawnstate(smode); - gs.lifesequence++; -} - void sendspawn(client *c) { + if(team_isspect(c->team)) return; clientstate &gs = c->state; - spawnstate(c); + gs.respawn(); + gs.spawnstate(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, @@ -350,7 +381,6 @@ void sendspawn(client *c) } // demo -bool demonextmatch = false; FILE *demotmp = NULL; gzFile demorecord = NULL, demoplayback = NULL; bool recordpackets = false; @@ -370,6 +400,11 @@ void recordpacket(int chan, void *data, int len) if(recordpackets) writedemo(chan, data, len); } +void recordpacket(int chan, ENetPacket *packet) +{ + if(recordpackets) writedemo(chan, packet->data, (int)packet->dataLength); +} + void enddemorecord() { if(!demorecord) return; @@ -383,6 +418,14 @@ void enddemorecord() #endif if(!demotmp) return; + if(gamemillis < DEMO_MINTIME) + { + fclose(demotmp); + demotmp = NULL; + logline(ACLOG_INFO, "Demo discarded."); + return; + } + fseek(demotmp, 0, SEEK_END); int len = ftell(demotmp); rewind(demotmp); @@ -391,9 +434,11 @@ void enddemorecord() delete[] demofiles[0].data; demofiles.remove(0); } + int mr = gamemillis >= gamelimit ? 0 : (gamelimit - gamemillis + 60000 - 1)/60000; demofile &d = demofiles.add(); s_sprintf(d.info)("%s: %s, %s, %.2f%s", asctime(), modestr(gamemode), smapname, len > 1024*1024 ? len/(1024*1024.f) : len/1024.0f, len > 1024*1024 ? "MB" : "kB"); s_sprintf(d.file)("%s_%s_%s", timestring(), behindpath(smapname), modestr(gamemode, true)); + if(mr) { s_strcatf(d.info, ", %d mr", mr); s_strcatf(d.file, "_%dmr", mr); } s_sprintfd(msg)("Demo \"%s\" recorded\nPress F10 to download it from the server..", d.info); sendservmsg(msg); logline(ACLOG_INFO, "Demo \"%s\" recorded.", d.info); @@ -422,7 +467,7 @@ void enddemorecord() void setupdemorecord() { - if(numlocalclients() || !m_mp(gamemode) || gamemode==1) return; + if(numlocalclients() || !m_mp(gamemode) || gamemode == GMODE_COOPEDIT) return; #ifdef WIN32 gzFile f = gzopen(path("demos/demorecord", true), "wb9"); @@ -459,6 +504,15 @@ void setupdemorecord() s_sprintf(desc)("%s, %s, %s %s", modestr(gamemode, true), behindpath(smapname), asctime(), servdesc_current); desc[DHDR_DESCCHARS - 1] = '\0'; strcpy(hdr.desc, desc); + memset(hdr.plist, 0, DHDR_PLISTCHARS); + const char *bl = ""; + loopv(clients) + { + client *ci = clients[i]; + if(ci->type==ST_EMPTY) continue; + if(strlen(hdr.plist) + strlen(ci->name) < DHDR_PLISTCHARS - 2) { strcat(hdr.plist, bl); strcat(hdr.plist, ci->name); } + bl = " "; + } gzwrite(demorecord, &hdr, sizeof(demoheader)); ENetPacket *packet = enet_packet_create(NULL, MAXTRANS, ENET_PACKET_FLAG_RELIABLE); @@ -477,8 +531,11 @@ void setupdemorecord() ucharbuf q(&buf[sizeof(header)], sizeof(buf)-sizeof(header)); putint(q, SV_INITC2S); sendstring(ci->name, q); - putint(q, ci->team); - putint(q, ci->skin); + putint(q, ci->skin[TEAM_CLA]); + putint(q, ci->skin[TEAM_RVSF]); + putint(q, SV_SETTEAM); + putint(q, ci->clientnum); + putint(q, ci->team | (FTR_INFO << 4)); ucharbuf h(header, sizeof(header)); putint(h, SV_CLIENT); @@ -660,7 +717,7 @@ void putflaginfo(ucharbuf &p, int flag) bool flagdistance(sflaginfo &f, int cn) { - if(!valid_client(cn)) return false; + if(!valid_client(cn) || m_demo) return false; client &c = *clients[cn]; vec v(-1, -1, c.state.o.z); switch(f.state) @@ -928,17 +985,7 @@ void htf_forceflag(int flag) } -bool canspawn(client *c, bool connecting = false) -{ - if(m_arena) - { - if(connecting && numauthedclients()<=2) return true; - return false; - } - return true; -} - -int arenaround = 0; +int arenaround = 0, arenaroundstartmillis = 0; struct twoint { int index, value; }; int cmpscore(const int *a, const int *b) { return clients[*a]->at3_score - clients[*b]->at3_score; } @@ -1001,15 +1048,12 @@ void arenacheck() if(arenaround) { // start new arena round arenaround = 0; + arenaroundstartmillis = gamemillis; distributespawns(); - loopv(clients) if(clients[i]->type!=ST_EMPTY && clients[i]->isauthed && team_isactive(clients[i]->team)) + loopv(clients) if(clients[i]->type!=ST_EMPTY && clients[i]->isauthed) { - if(clients[i]->isonrightmap) - { - clients[i]->state.respawn(); + if(clients[i]->isonrightmap && team_isactive(clients[i]->team)) sendspawn(clients[i]); - } - else sendservmsg("\f3you have to be on the right map to spawn: type /getmap", i); // FIXME } return; } @@ -1056,6 +1100,80 @@ void arenacheck() if(autoteam && m_teammode) refillteams(true); } +//#define LMS_CLEARATROUNDSTART +#define LMS_ITEMMAXAGE 2 + +void lmscheck() // spawn random item class once per round +{ + const int types[] = { I_CLIPS, I_AMMO, I_GRENADE, I_GRENADE, I_HEALTH, I_ARMOUR, I_AKIMBO }, types_n = sizeof(types) / sizeof(types[0]); // double probability for grenades ;) + static bool cleared = false, announced = false, spawned = false; +#ifndef LMS_CLEARATROUNDSTART + static int lasttypes[LMS_ITEMMAXAGE + 1] = { NOTUSED }; +#endif + if(!m_lms || !numactiveclients()) return; + if(arenaround || !arenaroundstartmillis) // during intermission: + { + cleared = false; + arenaroundstartmillis |= 1; + } + else if(gamemillis - arenaroundstartmillis < 5 * 1000) // at round start: clear remaining items from former rounds + { + if(!cleared) + { +#ifdef LMS_CLEARATROUNDSTART + loopv(sents) sents[i].spawned = sents[i].legalpickup = false; + sendf(-1, 1, "riii", SV_LMSITEM, LMSITEM_CLEAR, NOTUSED); +#else + bool doclear = lasttypes[LMS_ITEMMAXAGE] != NOTUSED; + loopv(sents) + { + if(!sents[i].spawned || (doclear && lasttypes[LMS_ITEMMAXAGE] == sents[i].type) > 2) sents[i].spawned = sents[i].legalpickup = false; + else sents[i].legalpickup = sents[i].spawned; + } + if(doclear) sendf(-1, 1, "riii", SV_LMSITEM, LMSITEM_CLEAR, lasttypes[LMS_ITEMMAXAGE]); +#endif + announced = false; + spawned = false; + } + cleared = true; + } + else if(gamemillis - arenaroundstartmillis < 15 * 1000) // 5 seconds after round start: announce item class + { + if(!announced) + { + int avail = 0; + loopi(types_n) if(smapstats.entcnt[types[i]]) avail++; + int pick = rnd(avail + avail / 2 + 1); + lmsitemtype = NOTUSED; + if(pick < avail) + { + loopi(types_n) if(smapstats.entcnt[types[i]] && !pick--) lmsitemtype = types[i]; + } + if(avail) sendf(-1, 1, "riii", SV_LMSITEM, LMSITEM_ANNOUNCE, lmsitemtype); +#ifndef LMS_CLEARATROUNDSTART + for(int i = LMS_ITEMMAXAGE - 1; i >= 0; i--) lasttypes[i + 1] = lasttypes[i] == lmsitemtype ? NOTUSED : lasttypes[i]; + lasttypes[0] = lmsitemtype; +#endif + } + announced = true; + cleared = false; + } + else // 15 seconds after round start: spawn items + { + if(!spawned && lmsitemtype != NOTUSED) + { +#ifdef LMS_CLEARATROUNDSTART + loopv(sents) sents[i].spawned = sents[i].legalpickup = sents[i].type == lmsitemtype; +#else + loopv(sents) if(sents[i].type == lmsitemtype) sents[i].spawned = sents[i].legalpickup = true; +#endif + sendf(-1, 1, "riii", SV_LMSITEM, LMSITEM_SPAWN, lmsitemtype); + logline(ACLOG_INFO, "spawned all \"%s\" items", entnames[lmsitemtype]); + } + spawned = true; + } +} + #define SPAMREPEATINTERVAL 20 // detect doubled lines only if interval < 20 seconds #define SPAMMAXREPEAT 3 // 4th time is SPAM #define SPAMCHARPERMINUTE 220 // good typist @@ -1085,18 +1203,48 @@ bool spamdetect(client *cl, char *text) // checks doubled lines and average typi return spam; } -void sendteamtext(char *text, int sender) +// chat message distribution matrix: +// +// /------------------------ common chat C c C c c C C c C +// |/----------------------- RVSF chat T +// ||/---------------------- CLA chat T +// |||/--------------------- spect chat t t t T t T +// |||| | | | | | | | | | +// |||| | | | | | | | | | C: normal chat +// |||| team modes: chat goes to | | | | | | | | | T: team chat +// XX --> RVSF players >-----/ | | | | | | | | c: normal chat in all mastermodes except 'match' +// XX X --> RVSF spect players >-------/ | | | | | | | t: all chat in mastermode 'match', otherwise only team chat +// X X --> CLA players >---------/ | | | | | | +// X XX --> CLA spect players >-----------/ | | | | | +// X X --> SPECTATORs >-------------/ | | | | +// XXXX --> SPECTATORs (admin) >---------------/ | | | +// ffa modes: | | | +// X --> any player (ffa mode) >-----------------/ | | +// X X --> any spectator (ffa mode) >-------------------/ | +// X X --> admin spectator >---------------------/ + +// purpose: +// a) give spects a possibility to chat without annoying the players (even in ffa), +// b) no hidden messages from spects to active teams, +// c) no spect talk to players during 'match' + +void sendteamtext(char *text, int sender, int msgtype) { if(!valid_client(sender) || clients[sender]->team == TEAM_VOID) return; ENetPacket *packet = enet_packet_create(NULL, MAXTRANS, ENET_PACKET_FLAG_RELIABLE); ucharbuf p(packet->data, packet->dataLength); - putint(p, SV_TEAMTEXT); + putint(p, msgtype); putint(p, sender); sendstring(text, p); enet_packet_resize(packet, p.length()); + recordpacket(1, packet); + int &st = clients[sender]->team; loopv(clients) if(i!=sender) { - if(clients[i]->team == clients[sender]->team || !m_teammode) // send to everyone in non-team mode + 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 sendpacket(i, 1, packet); } if(packet->referenceCount==0) enet_packet_destroy(packet); @@ -1147,7 +1295,7 @@ bool serverpickup(int i, int sender) // server side item pickup, acknowl server_entity &e = sents[i]; if(!e.spawned) { - if(!e.spawntime && hn) logline(ACLOG_INFO, "[%s] tried to pick up entity #%d - can't be picked up in this gamemode or at all", hn, i); + 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]); return false; } if(sender>=0) @@ -1156,22 +1304,19 @@ bool serverpickup(int i, int sender) // server side item pickup, acknowl if(cl->type==ST_TCPIP) { if(cl->state.state!=CS_ALIVE || !cl->state.canpickup(e.type)) return false; - if(e.hascoord) + vec v(e.x, e.y, cl->state.o.z); + float dist = cl->state.o.dist(v); + if(dist > 10 && !m_demo) // <2.5 would be normal, LAG may increase the value { - vec v(e.x, e.y, cl->state.o.z); - float dist = cl->state.o.dist(v); - if(dist > 10) // <2.5 would be normal, LAG may increase the value - { - cl->farpickups++; - logline(ACLOG_INFO, "[%s] %s picked up entity type %d #%d, distance %.2f (%d)", cl->hostname, cl->name, e.type, i, dist, cl->farpickups); - } + cl->farpickups++; + logline(ACLOG_INFO, "[%s] %s picked up entity #%d (%s), distance %.2f (%d)", cl->hostname, cl->name, i, entnames[e.type], dist, cl->farpickups); } } sendf(-1, 1, "ri3", SV_ITEMACC, i, sender); cl->state.pickup(sents[i].type); } e.spawned = false; - e.spawntime = spawntime(e.type); + if(!m_lms) e.spawntime = spawntime(e.type); return true; } @@ -1278,13 +1423,93 @@ void updatesdesc(const char *newdesc, ENetAddress *caller = NULL) } } -void forceteam(int client, int team, bool respawn, bool notify = false) +int canspawn(client *c) // beware: canspawn() doesn't check m_arena! { - if(!valid_client(client) || team < 0 || team > 1) return; - if(clients[client]->lastforce && (servmillis - clients[client]->lastforce) < 2000) return; - sendf(client, 1, "riii", SV_FORCETEAM, team, (respawn ? 1 : 0) | (respawn && !notify ? 2 : 0)); - clients[client]->lastforce = servmillis; - if(notify) sendf(-1, 1, "riii", SV_FORCENOTIFY, client, team); + if(!c || c->type == ST_EMPTY || !c->isauthed || !team_isvalid(c->team)) return -1; + if(!c->isonrightmap) return SP_WRONGMAP; + if(mastermode == MM_MATCH && 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; + else return SP_REFILLMATCH; + } + } + return SP_OK; +} + +void updatespawnpermissions() +{ + loopv(clients) if(clients[i]->type!=ST_EMPTY && clients[i]->isauthed) + { + client *c = clients[i]; + int sp = canspawn(c); + if(servmillis - c->spawnpermsent > 1000 && sp != c->spawnperm) + { + sendf(i, 1, "rii", SV_SPAWNDENY, sp); + c->spawnperm = sp; + } + } +} + +bool updateclientteam(int cln, int newteam, int ftr) +{ + if(!valid_client(cln)) return false; + if(!team_isvalid(newteam) && newteam != TEAM_ANYACTIVE) newteam = TEAM_SPECT; + client &cl = *clients[cln]; + if(cl.team == newteam && ftr != FTR_AUTOTEAM) return true; // no change + if(cl.lastforce && servmillis - cl.lastforce < 2000) return false; // wait for the last change to take effect + int *teamsizes = numteamclients(cln); + if(newteam == TEAM_ANYACTIVE) // when spawning from spect + { + if(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; + else + { // join weaker team + int teamscore[2] = {0, 0}, sum = calcscores(); + loopv(clients) if(clients[i]->type!=ST_EMPTY && i != cln && clients[i]->isauthed && clients[i]->team != TEAM_SPECT) + { + teamscore[team_base(clients[i]->team)] += clients[i]->at3_score; + } + newteam = sum > 200 ? (teamscore[TEAM_CLA] < teamscore[TEAM_RVSF] ? TEAM_CLA : TEAM_RVSF) : rnd(2); + } + } + } + if(ftr == FTR_PLAYERWISH) + { + if(mastermode == MM_MATCH && matchteamsize) + { + 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(mastermode == MM_MATCH) + { + if(matchteamsize && teamsizes[newteam] >= matchteamsize) return false; // ensure maximum team size + } + else + { + if(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 + } + } + if(cl.team == newteam && ftr != FTR_AUTOTEAM) return true; // no change + if(team_isspect(newteam) || (team_isactive(newteam) && team_isactive(cl.team))) forcedeath(&cl); + sendf(-1, 1, "riii", SV_SETTEAM, cln, newteam | ((ftr == FTR_SILENTFORCE ? FTR_INFO : ftr) << 4)); + if(!team_isspect(newteam) && team_isspect(cl.team) && ftr != FTR_INFO) sendspawn(&cl); + cl.team = newteam; + cl.lastforce = servmillis; + return true; } int calcscores() // skill eval @@ -1303,19 +1528,19 @@ int calcscores() // skill eval ivector shuffle; -void shuffleteams(bool respawn = true) +void shuffleteams(int ftr = FTR_AUTOTEAM) { int numplayers = numclients(); int team, sums = calcscores(); if(gamemillis < 2 * 60 *1000) { // random int teamsize[2] = {0, 0}; - loopv(clients) if(clients[i]->type!=ST_EMPTY) + loopv(clients) if(clients[i]->type!=ST_EMPTY && clients[i]->isonrightmap && !team_isspect(clients[i]->team)) // only shuffle active players { sums += rnd(1000); team = sums & 1; if(teamsize[team] >= numplayers/2) team = team_opposite(team); - forceteam(i, team, respawn); + updateclientteam(i, team, ftr); teamsize[team]++; sums >>= 1; } @@ -1325,18 +1550,23 @@ void shuffleteams(bool respawn = true) shuffle.setsize(0); sums /= 4 * numplayers + 2; team = rnd(2); - loopv(clients) if(clients[i]->type!=ST_EMPTY) { clients[i]->at3_score += rnd(sums | 1); shuffle.add(i); } + loopv(clients) if(clients[i]->type!=ST_EMPTY && clients[i]->isonrightmap && !team_isspect(clients[i]->team)) + { + clients[i]->at3_score += rnd(sums | 1); + shuffle.add(i); + } shuffle.sort(cmpscore); loopi(shuffle.length()) { - forceteam(shuffle[i], team, respawn); + updateclientteam(shuffle[i], team, ftr); team = !team; } } } -bool refillteams(bool now, bool notify) // force only minimal amounts of players +bool refillteams(bool now, int ftr) // force only minimal amounts of players { + if(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; @@ -1392,14 +1622,15 @@ bool refillteams(bool now, bool notify) // force only minimal amounts of player } if(pick < 0) break; // should really never happen // move picked player - forceteam(pick, !bigteam, true, notify); - - diffnum -= 2; - diffscore -= 2 * clients[pick]->at3_score; - moveable[bigteam]--; clients[pick]->at3_dontmove = true; - clients[pick]->at3_lastforce = gamemillis; // try not to force this player again for the next 5 minutes - switched = true; + moveable[bigteam]--; + if(updateclientteam(pick, !bigteam, ftr)) + { + 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 + switched = true; + } } } } @@ -1419,19 +1650,36 @@ void resetserver(const char *newname, int newmode, int newtime) minremain = newtime >= 0 ? newtime : (m_teammode ? 15 : 10); gamemillis = 0; gamelimit = minremain*60000; + arenaround = arenaroundstartmillis = 0; + memset(&smapstats, 0, sizeof(smapstats)); interm = 0; if(!laststatus) laststatus = servmillis-61*1000; lastfillup = servmillis; sents.setsize(0); - savedscores.setsize(0); + if(mastermode == MM_PRIVATE) + { + loopv(savedscores) savedscores[i].valid = false; + } + else savedscores.setsize(0); ctfreset(); + + nextmapname[0] = '\0'; + forceintermission = false; +} + +void startdemoplayback(const char *newname) +{ + if(isdedicated) return; + resetserver(newname, GMODE_DEMO, -1); + setupdemoplayback(); } void startgame(const char *newname, int newmode, int newtime, bool notify) { + if(!newname || !*newname || newmode == GMODE_DEMO) fatal("startgame() abused"); bool lastteammode = m_teammode; - resetserver(newname, newmode, newtime); + resetserver(newname, newmode, newtime); // beware: may clear *newname if(custom_servdesc && findcnbyaddress(&servdesc_caller) < 0) { @@ -1443,9 +1691,10 @@ void startgame(const char *newname, int newmode, int newtime, bool notify) } } + int maploc = MAP_VOID; + mapstats *ms = getservermapstats(smapname, isdedicated, &maploc); mapbuffer.clear(); - if(isdedicated) mapbuffer.load(); - mapstats *ms = getservermapstats(smapname, isdedicated); + if(isdedicated && distributablemap(maploc)) mapbuffer.load(); if(ms) { smapstats = *ms; @@ -1466,59 +1715,46 @@ void startgame(const char *newname, int newmode, int newtime, bool notify) { e.type = smapstats.enttypes[i]; e.transformtype(smode); - server_entity se = { e.type, false, true, 0, smapstats.entposs[i * 3], smapstats.entposs[i * 3 + 1]}; + server_entity se = { e.type, false, false, 0, smapstats.entposs[i * 3], smapstats.entposs[i * 3 + 1]}; sents.add(se); - if(e.fitsmode(smode)) sents[i].spawned = true; + if(e.fitsmode(smode)) sents[i].spawned = sents[i].legalpickup = true; } mapbuffer.setrevision(); } - else - { - memset(&smapstats, 0, sizeof(smapstats)); - if(isdedicated) sendservmsg("\f3server error: map not found - please start another map"); - } + else if(isdedicated) sendservmsg("\f3server error: map not found - please start another 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, "ri2", SV_TIMEUP, minremain); } - logline(ACLOG_INFO, "\nGame start: %s on %s, %d players, %d minutes, mastermode %d, (map rev %d/%d, itemlist %spreloaded, 'getmap' %sprepared)", - modestr(smode), smapname, numclients(), minremain, mastermode, smapstats.hdr.maprevision, smapstats.cgzsize, ms ? "" : "not ", mapbuffer.available() ? "" : "not "); - - arenaround = 0; - if(m_arena) - { - distributespawns(); - } + s_sprintfd(gsmsg)("Game start: %s on %s, %d players, %d minutes, mastermode %d, ", modestr(smode), smapname, numclients(), minremain, mastermode); + if(mastermode == MM_PRIVATE) s_strcatf(gsmsg, "teamsize %d, ", matchteamsize); + if(ms) s_strcatf(gsmsg, "(map rev %d/%d, %s, 'getmap' %sprepared)", smapstats.hdr.maprevision, smapstats.cgzsize, maplocstr[maploc], mapbuffer.available() ? "" : "not "); + else s_strcatf(gsmsg, "error: failed to preload map (map: %s)", maplocstr[maploc]); + logline(ACLOG_INFO, "\n%s", gsmsg); + if(m_arena) distributespawns(); if(notify) { // shuffle if previous mode wasn't a team-mode if(m_teammode) { if(!lastteammode) - shuffleteams(false); + shuffleteams(FTR_INFO); else if(autoteam) - refillteams(true, false); + refillteams(true, FTR_INFO); } // send spawns loopv(clients) if(clients[i]->type!=ST_EMPTY) { client *c = clients[i]; c->mapchange(); - if(m_mp(smode)) sendspawn(c); + if(m_mp(smode) && team_isactive(c->team) && c->isonrightmap) sendspawn(c); } } - if(m_demo) setupdemoplayback(); - else if((demonextmatch || scl.demoeverymatch) && *newname && numnonlocalclients() > 0) - { - demonextmatch = false; - setupdemorecord(); - } + if(numnonlocalclients() > 0) setupdemorecord(); if(notify && m_ktf) sendflaginfo(); - - nextmapname[0] = '\0'; - forceintermission = false; + if(notify) senddisconnectedscores(-1); } bool isbanned(int cn) @@ -1555,6 +1791,7 @@ struct voteinfo serveraction *action; voteinfo() : owner(0), callmillis(0), result(VOTE_NEUTRAL), action(NULL) {} + ~voteinfo() { delete action; } void end(int result) { @@ -1684,6 +1921,32 @@ void changeclientrole(int client, int role, char *pwd, bool force) if(curvote) curvote->evaluate(); } +void senddisconnectedscores(int cn) +{ + ENetPacket *packet = enet_packet_create(NULL, MAXTRANS, ENET_PACKET_FLAG_RELIABLE); + ucharbuf p(packet->data, packet->dataLength); + putint(p, SV_DISCSCORES); + if(mastermode == MM_MATCH) + { + loopv(savedscores) + { + savedscore &sc = savedscores[i]; + if(sc.valid) + { + putint(p, sc.team); + sendstring(sc.name, p); + putint(p, sc.flagscore); + putint(p, sc.frags); + putint(p, sc.deaths); + } + } + } + putint(p, -1); + enet_packet_resize(packet, p.length()); + sendpacket(cn, 1, packet); + if(!packet->referenceCount) enet_packet_destroy(packet); +} + const char *disc_reason(int reason) { static const char *disc_reasons[] = { "normal", "end of packet", "client num", "kicked by server operator", "banned by server operator", "tag type", "connection refused due to ban", "wrong password", "failed admin login", "server FULL - maxclients", "server mastermode is \"private\"", "auto kick - did your score drop below the threshold?", "auto ban - did your score drop below the threshold?", "duplicate connection", "inappropriate nickname" }; @@ -1701,7 +1964,7 @@ void disconnect_client(int n, int reason) savedscore *sc = findscore(c, true); if(sc) { - sc->save(c.state); + sc->save(c.state, c.team); scoresaved = ", score saved"; } } @@ -1713,6 +1976,7 @@ void disconnect_client(int n, int reason) clients[n]->zap(); sendf(-1, 1, "rii", SV_CDIS, n); if(curvote) curvote->evaluate(); + if(*scoresaved && mastermode == MM_MATCH) senddisconnectedscores(-1); } void sendwhois(int sender, int cn) @@ -1749,7 +2013,7 @@ void sendinits2c(client &c) sendf(c.clientnum, 1, "ri5", SV_INITS2C, c.clientnum, isdedicated ? SERVER_PROTOCOL_VERSION : PROTOCOL_VERSION, c.salt, scl.serverpassword[0] ? 1 : 0); } -void welcomepacket(ucharbuf &p, int n, ENetPacket *packet, bool forcedeath) +void welcomepacket(ucharbuf &p, int n, ENetPacket *packet) { #define CHECKSPACE(n) \ { \ @@ -1797,49 +2061,27 @@ void welcomepacket(ucharbuf &p, int n, ENetPacket *packet, bool forcedeath) loopi(2) putflaginfo(p, i); } } - if(c && c->type == ST_TCPIP && serveroperator() != -1) sendserveropinfo(n); - if(numcl>1) - { - putint(p, SV_FORCETEAM); - putint(p, freeteam(n)); - putint(p, 0); - } - if(c) c->lastforce = servmillis; + savedscore *sc = NULL; bool restored = false; if(c) { - if(c->type==ST_TCPIP) + if(c->type == ST_TCPIP && serveroperator() != -1) sendserveropinfo(n); + sc = findscore(*c, false); + c->team = mastermode == MM_MATCH && sc ? team_tospec(sc->team) : TEAM_SPECT; + putint(p, SV_SETTEAM); + putint(p, n); + putint(p, c->team | (FTR_INFO << 4)); + if(sc && sc->valid) { - savedscore *sc = findscore(*c, false); - if(sc) - { - sc->restore(c->state); - restored = true; - } + sc->restore(c->state); + sc->valid = false; + restored = true; } CHECKSPACE(256); - if(!canspawn(c, true) || forcedeath) - { - putint(p, SV_FORCEDEATH); - putint(p, n); - sendf(-1, 1, "ri2x", SV_FORCEDEATH, n, n); - } - else - { - clientstate &gs = c->state; - spawnstate(c); - putint(p, SV_SPAWNSTATE); - putint(p, gs.lifesequence); - putint(p, gs.health); - putint(p, gs.armour); - putint(p, gs.primary); - putint(p, gs.gunselect); - putint(p, -1); - loopi(NUMGUNS) putint(p, gs.ammo[i]); - loopi(NUMGUNS) putint(p, gs.mag[i]); - gs.lastspawn = gamemillis; - } + putint(p, SV_FORCEDEATH); + putint(p, n); + sendf(-1, 1, "ri2x", SV_FORCEDEATH, n, n); } if(clients.length()>1 || restored) { @@ -1864,8 +2106,8 @@ void welcomepacket(ucharbuf &p, int n, ENetPacket *packet, bool forcedeath) } putint(p, -1); } - putint(p, SV_AUTOTEAM); - putint(p, autoteam ? 1 : 0); + putint(p, SV_SERVERMODE); + putint(p, sendservermode(false)); const char *motd = scl.motd[0] ? scl.motd : infofiles.getmotd(c ? c->lang : ""); if(motd) { @@ -1877,11 +2119,11 @@ void welcomepacket(ucharbuf &p, int n, ENetPacket *packet, bool forcedeath) #undef CHECKSPACE } -void sendwelcome(client *cl, int chan, bool forcedeath) +void sendwelcome(client *cl, int chan) { ENetPacket *packet = enet_packet_create(NULL, MAXTRANS, ENET_PACKET_FLAG_RELIABLE); ucharbuf p(packet->data, packet->dataLength); - welcomepacket(p, cl->clientnum, packet, forcedeath); + welcomepacket(p, cl->clientnum, packet); enet_packet_resize(packet, p.length()); sendpacket(cl->clientnum, chan, packet); if(!packet->referenceCount) enet_packet_destroy(packet); @@ -1904,14 +2146,14 @@ int checktype(int type, client *cl) // server only messages static int servtypes[] = { SV_INITS2C, SV_WELCOME, SV_CDIS, SV_GIBDIED, SV_DIED, SV_GIBDAMAGE, SV_DAMAGE, SV_HITPUSH, SV_SHOTFX, - SV_SPAWNSTATE, SV_FORCEDEATH, SV_RESUME, SV_TIMEUP, - SV_ITEMACC, SV_MAPCHANGE, SV_ITEMSPAWN, SV_PONG, + 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_ARENAWIN, SV_SERVOPINFO, + SV_ARENAWIN, SV_LMSITEM, SV_SERVOPINFO, SV_CALLVOTESUC, SV_CALLVOTEERR, SV_VOTERESULT, - SV_FORCETEAM, SV_AUTOTEAM, SV_WHOISINFO, + SV_SETTEAM, SV_TEAMDENY, SV_SERVERMODE, SV_WHOISINFO, SV_SENDDEMOLIST, SV_SENDDEMO, SV_DEMOPLAYBACK, - SV_CLIENT, SV_FORCENOTIFY }; + SV_CLIENT }; if(cl) loopi(sizeof(servtypes)/sizeof(int)) if(type == servtypes[i]) return -1; if (type < 0 || type >= SV_NUM) return -1; return type; @@ -1920,7 +2162,7 @@ int checktype(int type, client *cl) // 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) -void process(ENetPacket *packet, int sender, int chan) // sender may be -1 +void process(ENetPacket *packet, int sender, int chan) { ucharbuf p(packet->data, packet->dataLength); char text[MAXTRANS]; @@ -1952,10 +2194,16 @@ void process(ENetPacket *packet, int sender, int chan) // sender may be -1 cl->state.nextprimary = getint(p); bool banned = isbanned(sender); bool srvfull = numnonlocalclients() > scl.maxclients; - bool srvprivate = mastermode == MM_PRIVATE; + bool srvprivate = mastermode == MM_PRIVATE || mastermode == MM_MATCH; + bool matchreconnect = mastermode == MM_MATCH && findscore(*cl, false); int bl = 0, wl = nickblacklist.checkwhitelist(*cl); if(wl == NWL_PASS) s_strcat(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); + } 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); @@ -2017,8 +2265,11 @@ void process(ENetPacket *packet, int sender, int chan) // sender may be -1 sendwelcome(cl); if(findscore(*cl, false)) sendresume(*cl, true); if(clientrole != CR_DEFAULT) changeclientrole(sender, clientrole, NULL, true); + if(cl->type==ST_TCPIP) senddisconnectedscores(sender); } + if(!cl) { logline(ACLOG_ERROR, " client in process()"); return; } // should never happen anyway + if(packet->flags&ENET_PACKET_FLAG_RELIABLE) reliablemessages = true; #define QUEUE_MSG { if(cl->type==ST_TCPIP) while(curmsgmessages.add(p.buf[curmsg++]); } @@ -2061,21 +2312,23 @@ void process(ENetPacket *packet, int sender, int chan) // sender may be -1 switch(type) { + case SV_TEAMTEXTME: case SV_TEAMTEXT: getstring(text, p); filtertext(text, text); if(!spamdetect(cl, text)) - { - logline(ACLOG_INFO, "[%s] %s says to team %s: '%s'", cl->hostname, cl->name, team_string(cl->team), text); - sendteamtext(text, sender); + { // 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); + sendteamtext(text, sender, type); } else { - logline(ACLOG_INFO, "[%s] %s says to team %s: '%s', SPAM detected", cl->hostname, cl->name, team_string(cl->team), text); + logline(ACLOG_INFO, "[%s] %s%s says to team %s: '%s', SPAM detected", cl->hostname, type == SV_TEAMTEXTME ? "(me) " : "", cl->name, team_string(cl->team), text); sendservmsg("\f3please do not spam", sender); } break; + case SV_TEXTME: case SV_TEXT: { int mid1 = curmsg, mid2 = p.length(); @@ -2083,13 +2336,21 @@ void process(ENetPacket *packet, int sender, int chan) // sender may be -1 filtertext(text, text); if(!spamdetect(cl, text)) { - logline(ACLOG_INFO, "[%s] %s says: '%s'", cl->hostname, cl->name, text); - if(cl->type==ST_TCPIP) while(mid1messages.add(p.buf[mid1++]); - QUEUE_STR(text); + if(mastermode != MM_MATCH || !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); + if(cl->type==ST_TCPIP) while(mid1messages.add(p.buf[mid1++]); + QUEUE_STR(text); + } + 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); + sendteamtext(text, sender, type == SV_TEXTME ? SV_TEAMTEXTME : SV_TEAMTEXT); + } } else { - logline(ACLOG_INFO, "[%s] %s says: '%s', SPAM detected", cl->hostname, cl->name, text); + logline(ACLOG_INFO, "[%s] %s%s says: '%s', SPAM detected", cl->hostname, type == SV_TEXTME ? "(me) " : "", cl->name, text); sendservmsg("\f3please do not spam", sender); } break; @@ -2114,9 +2375,8 @@ void process(ENetPacket *packet, int sender, int chan) // sender may be -1 bool namechanged = strcmp(cl->name, text) != 0; if(namechanged) logline(ACLOG_INFO,"[%s] %s changed his name to %s", cl->hostname, cl->name, text); s_strncpy(cl->name, text, MAXNAMELEN+1); - cl->team = getint(p); - if(!team_isvalid(cl->team)) cl->team = TEAM_VOID; - cl->skin = getint(p); + cl->skin[TEAM_CLA] = getint(p); + cl->skin[TEAM_RVSF] = getint(p); QUEUE_MSG; if(namechanged) { @@ -2147,11 +2407,21 @@ void process(ENetPacket *packet, int sender, int chan) // sender may be -1 { int gzs = getint(p); int rev = getint(p); - if(!isdedicated || (smapstats.cgzsize == gzs && smapstats.hdr.maprevision == rev)) cl->isonrightmap = true; + if(!isdedicated || (smapstats.cgzsize == gzs && smapstats.hdr.maprevision == rev)) + { + cl->isonrightmap = true; + if(cl->loggedwrongmap) logline(ACLOG_INFO, "[%s] %s is now on the right map: revision %d/%d", cl->hostname, cl->name, rev, gzs); + if(numclients() < 2 && !m_demo) + { // spawn on empty servers + if(team_isspect(cl->team)) updateclientteam(cl->clientnum, TEAM_ANYACTIVE, FTR_INFO); + sendspawn(cl); + } + } else { forcedeath(cl); logline(ACLOG_INFO, "[%s] %s is on the wrong map: revision %d/%d", cl->hostname, cl->name, rev, gzs); + cl->loggedwrongmap = true; } QUEUE_MSG; break; @@ -2191,15 +2461,19 @@ void process(ENetPacket *packet, int sender, int chan) // sender may be -1 break; } - case SV_CHANGETEAM: - if(cl->state.state==CS_ALIVE) forcedeath(cl); + case SV_TRYTEAM: + { + int t = getint(p); + if(!updateclientteam(sender, team_isvalid(t) ? t : TEAM_SPECT, FTR_PLAYERWISH)) sendf(sender, 1, "rii", SV_TEAMDENY, t); break; + } case SV_TRYSPAWN: - if(!cl->isonrightmap) sendservmsg("\f3you can't spawn until you download the map from the server; please type /getmap", sender); - if(cl->state.state!=CS_DEAD || cl->state.lastspawn>=0 || !canspawn(cl) || !cl->isonrightmap) break; - if(cl->state.lastdeath) cl->state.respawn(); - sendspawn(cl); + if(team_isspect(cl->team) && canspawn(cl) < SP_OK_NUM) + { + updateclientteam(sender, TEAM_ANYACTIVE, FTR_PLAYERWISH); + } + if(!m_arena && canspawn(cl) < SP_OK_NUM) sendspawn(cl); break; case SV_SPAWN: @@ -2221,6 +2495,11 @@ void process(ENetPacket *packet, int sender, int chan) // sender may be -1 }); break; } + case SV_SPECTCN: + cl->spectcn = getint(p); + logline(ACLOG_DEBUG, "[%s] %s is now spectating cn %d", cl->hostname, cl->name, cl->spectcn); // FIXME: comment out later + QUEUE_MSG; + break; case SV_SUICIDE: { @@ -2340,7 +2619,7 @@ void process(ENetPacket *packet, int sender, int chan) // sender may be -1 cl->position.setsizenodelete(0); while(curmsgposition.add(p.buf[curmsg++]); } - if(maplayout) + if(maplayout && !m_demo) { vec &po = clients[cn]->state.o; int ls = (1 << maplayout_factor) - 1; @@ -2424,10 +2703,10 @@ void process(ENetPacket *packet, int sender, int chan) // sender may be -1 { resetflag(cl->clientnum); // drop ctf flag savedscore *sc = findscore(*cl, true); // save score - if(sc) sc->save(cl->state); + if(sc) sc->save(cl->state, cl->team); sendpacket(cl->clientnum, 2, mappacket); - cl->mapchange(); - sendwelcome(cl, 2, true); // resend state properly + cl->mapchange(true); + sendwelcome(cl, 2); // resend state properly } else sendservmsg("no map to get", cl->clientnum); break; @@ -2491,7 +2770,7 @@ void process(ENetPacket *packet, int sender, int chan) // sender may be -1 getstring(text, p); filtertext(text, text); int mode = getint(p); - if(mode==GMODE_DEMO) vi->action = new demoplayaction(text); + if(mode==GMODE_DEMO) vi->action = new demoplayaction(newstring(text)); else vi->action = new mapaction(newstring(text), mode, sender); break; } @@ -2595,6 +2874,18 @@ void process(ENetPacket *packet, int sender, int chan) // sender may be -1 getstring(text, p, n); if(valid_client(sender) && clients[sender]->role==CR_ADMIN) logline(ACLOG_INFO, "%s", text); } + 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) + { + changematchteamsize(atoi(text)); + s_sprintfd(msg)("match team size set to %d", matchteamsize); + sendservmsg(msg, -1); + } + } // else if() // add other extensions here @@ -2656,8 +2947,9 @@ void resetserverifempty() { loopv(clients) if(clients[i]->type!=ST_EMPTY) return; resetserver("", 0, 10); - mastermode = MM_OPEN; + matchteamsize = 0; autoteam = true; + changemastermode(MM_OPEN); nextmapname[0] = '\0'; } @@ -2714,6 +3006,68 @@ void loggamestatus(const char *reason) logline(ACLOG_INFO, ""); } +static unsigned char chokelog[MAXCLIENTS + 1] = { 0 }; + +void linequalitystats(int elapsed) +{ + static unsigned int chokes[MAXCLIENTS + 1] = { 0 }, spent[MAXCLIENTS + 1] = { 0 }, chokes_raw[MAXCLIENTS + 1] = { 0 }, spent_raw[MAXCLIENTS + 1] = { 0 }; + if(elapsed) + { // collect data + int c1 = 0, c2 = 0, r1 = 0, numc = 0; + loopv(clients) + { + client &c = *clients[i]; + if(c.type != ST_TCPIP) continue; + numc++; + enet_uint32 &rtt = c.peer->lastRoundTripTime, &throttle = c.peer->packetThrottle; + if(rtt < c.bottomRTT + c.bottomRTT / 3) + { + if(servmillis - c.connectmillis < 5000) + c.bottomRTT = rtt; + else + c.bottomRTT = (c.bottomRTT * 15 + rtt) / 16; // simple IIR + } + if(throttle < 22) c1++; + if(throttle < 11) c2++; + if(rtt > c.bottomRTT * 2 && rtt - c.bottomRTT > 300) r1++; + } + spent_raw[numc] += elapsed; + int t = numc < 7 ? numc : (numc + 1) / 2 + 3; + chokes_raw[numc] += ((c1 >= t ? c1 + c2 : 0) + (r1 >= t ? r1 : 0)) * elapsed; + } + else + { // calculate compressed statistics + s_sprintfd(msg)("Uplink quality [ "); + int ncs = 0; + loopj(scl.maxclients) + { + int i = j + 1; + int nc = chokes_raw[i] / 1000 / i; + chokes[i] += nc; + ncs += nc; + spent[i] += spent_raw[i] / 1000; + chokes_raw[i] = spent_raw[i] = 0; + int s = 0, c = 0; + if(spent[i]) + { + frexp((double)spent[i] / 30, &s); + if(s < 0) s = 0; + if(s > 15) s = 15; + if(chokes[i]) + { + frexp(((double)chokes[i]) / spent[i], &c); + c = 15 + c; + if(c < 0) c = 0; + if(c > 15) c = 15; + } + } + chokelog[i] = (s << 4) + c; + s_strcatf(msg, "%02X ", chokelog[i]); + } + logline(ACLOG_DEBUG, "%s] +%d", msg, ncs); + } +} + 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; @@ -2750,6 +3104,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(); + if(m_lms) lmscheck(); } if(curvote) @@ -2762,7 +3117,8 @@ void serverslice(uint timeout) // main server update, called from cube main lo if(forceintermission || ((smode>1 || (gamemode==0 && nonlocalclients)) && gamemillis-diff>0 && gamemillis/60000!=(gamemillis-diff)/60000)) checkintermission(); - if(interm && gamemillis>interm) + if(m_demo && !demoplayback) maprot.restart(); + else if(interm && gamemillis>interm) { loggamestatus("game finished"); if(demorecord) enddemorecord(); @@ -2772,6 +3128,7 @@ void serverslice(uint timeout) // main server update, called from cube main lo if(nextmapname[0]) startgame(nextmapname, nextgamemode); else maprot.next(); } + updatespawnpermissions(); resetserverifempty(); @@ -2781,6 +3138,13 @@ void serverslice(uint timeout) // main server update, called from cube main lo if(autoteam && m_teammode && !m_arena && !interm && servmillis - lastfillup > 5000 && refillteams()) lastfillup = servmillis; + static unsigned int lastThrottleEpoch = 0; + if(serverhost->bandwidthThrottleEpoch != lastThrottleEpoch) + { + if(lastThrottleEpoch) linequalitystats(serverhost->bandwidthThrottleEpoch - lastThrottleEpoch); + lastThrottleEpoch = serverhost->bandwidthThrottleEpoch; + } + if(servmillis-laststatus>60*1000) // display bandwidth stats, useful for server ops { laststatus = servmillis; @@ -2789,10 +3153,11 @@ void serverslice(uint timeout) // main server update, called from cube main lo { if(nonlocalclients) loggamestatus(NULL); logline(ACLOG_INFO, "Status at %s: %d remote clients, %.1f send, %.1f rec (K/sec);" - " Ping num: %d, %d send, %d rec; CSL num: %d, %d send, %d rec (bytes)", + " Ping: #%d|%d|%d; CSL: #%d|%d|%d (bytes)", timestring(true, "%d-%m-%Y %H:%M:%S"), nonlocalclients, bsend/60.0f/1024, brec/60.0f/1024, mnum, msend, mrec, cnum, csend, crec); mnum = msend = mrec = cnum = csend = crec = 0; + linequalitystats(0); } bsend = brec = 0; } @@ -2908,6 +3273,12 @@ void extping_maprot(ucharbuf &po) sendstring("", po); } +void extping_uplinkstats(ucharbuf &po) +{ + if(scl.maxclients > 3) + po.put(chokelog + 4, scl.maxclients - 3); // send logs for 4..n used slots +} + void extinfo_cnbuf(ucharbuf &p, int cn) { if(cn == -1) // add all available player ids @@ -2928,6 +3299,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; putint(p,EXT_PLAYERSTATS_RESP_STATS); // send player stats following putint(p,clients[i]->clientnum); //add player id putint(p,clients[i]->ping); //Ping @@ -2937,10 +3309,10 @@ void extinfo_statsbuf(ucharbuf &p, int pid, int bpos, ENetSocket &pongsock, ENet putint(p,clients[i]->state.flagscore); //Flagscore putint(p,clients[i]->state.deaths); //Death putint(p,clients[i]->state.teamkills); //Teamkills - putint(p,clients[i]->state.damage*100/max(clients[i]->state.shotdamage,1)); //Accuracy - putint(p,clients[i]->state.health); //Health - putint(p,clients[i]->state.armour); //Armour - putint(p,clients[i]->state.gunselect); //Gun selected + putint(p,ismatch ? 0 : clients[i]->state.damage*100/max(clients[i]->state.shotdamage,1)); //Accuracy + putint(p,ismatch ? 0 : clients[i]->state.health); //Health + putint(p,ismatch ? 0 : clients[i]->state.armour); //Armour + putint(p,ismatch ? 0 : clients[i]->state.gunselect); //Gun selected putint(p,clients[i]->role); //Role putint(p,clients[i]->state.state); //State (Alive,Dead,Spawning,Lagged,Editing) uint ip = clients[i]->peer->address.host; // only 3 byte of the ip address (privacy protected) @@ -2989,6 +3361,7 @@ void localdisconnect() void localconnect() { modprotocol = false; + servstate.reset(); client &c = addclient(); c.type = ST_LOCAL; c.role = CR_ADMIN; @@ -3000,6 +3373,7 @@ void localconnect() void initserver(bool dedicated) { srand(time(NULL)); + smapname[0] = '\0'; string identity; if(scl.logident[0]) filtertext(identity, scl.logident, 0); @@ -3027,7 +3401,7 @@ void initserver(bool dedicated) nickblacklist.init(scl.nbfile); infofiles.init(scl.infopath, scl.motdpath); infofiles.getinfo("en"); // cache 'en' serverinfo - if(scl.demoeverymatch) logline(ACLOG_VERBOSE, "recording demo of every game (holding up to %d in memory)", scl.maxdemos); + 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); diff --git a/source/src/server.h b/source/src/server.h index 12c52e93..31835f7e 100644 --- a/source/src/server.h +++ b/source/src/server.h @@ -11,6 +11,8 @@ enum { GE_NONE = 0, GE_SHOT, GE_EXPLODE, GE_HIT, GE_AKIMBO, GE_RELOAD, GE_SCOPING, GE_SUICIDE, GE_PICKUP }; enum { ST_EMPTY, ST_LOCAL, ST_TCPIP }; +extern int smode, servmillis; + struct shotevent { int type; @@ -119,7 +121,7 @@ struct clientstate : playerstate int lastdeath, lastspawn, lifesequence; int lastshot; projectilestate<8> grenades; - int akimbos, akimbomillis; + int akimbomillis; bool scoped; int flagscore, frags, teamkills, deaths, shotdamage, damage; @@ -142,7 +144,6 @@ struct clientstate : playerstate state = CS_DEAD; lifesequence = -1; grenades.reset(); - akimbos = 0; akimbomillis = 0; scoped = false; flagscore = frags = teamkills = deaths = shotdamage = damage = 0; @@ -156,7 +157,6 @@ struct clientstate : playerstate lastdeath = 0; lastspawn = -1; lastshot = 0; - akimbos = 0; akimbomillis = 0; scoped = false; } @@ -166,9 +166,10 @@ struct savedscore { string name; uint ip; - int frags, flagscore, deaths, teamkills, shotdamage, damage; + int frags, flagscore, deaths, teamkills, shotdamage, damage, team; + bool valid; - void save(clientstate &cs) + void save(clientstate &cs, int t) { frags = cs.frags; flagscore = cs.flagscore; @@ -176,6 +177,8 @@ struct savedscore teamkills = cs.teamkills; shotdamage = cs.shotdamage; damage = cs.damage; + team = t; + valid = true; } void restore(clientstate &cs) @@ -199,18 +202,19 @@ struct client // server side version of "dynent" type int team; char lang[3]; int ping; - int skin; + int skin[2]; int vote; int role; int connectmillis; int acversion, acbuildtype; bool isauthed; // for passworded servers bool haswelcome; - bool isonrightmap; + bool isonrightmap, loggedwrongmap; bool timesync; int gameoffset, lastevent, lastvotecall; int demoflags; clientstate state; + int spectcn; vector events; vector position, messages; string lastsaytext; @@ -218,9 +222,11 @@ struct client // server side version of "dynent" type int at3_score, at3_lastforce, lastforce; bool at3_dontmove; int spawnindex; + int spawnperm, spawnpermsent; int salt; string pwd; int mapcollisions, farpickups; + enet_uint32 bottomRTT; gameevent &addevent() { @@ -229,12 +235,15 @@ struct client // server side version of "dynent" type return events.add(); } - void mapchange() + void mapchange(bool getmap = false) { state.reset(); events.setsizenodelete(0); timesync = false; isonrightmap = false; + spawnperm = SP_WRONGMAP; + spawnpermsent = servmillis; + if(!getmap) loggedwrongmap = false; lastevent = 0; at3_lastforce = 0; mapcollisions = farpickups = 0; @@ -243,9 +252,9 @@ struct client // server side version of "dynent" type void reset() { name[0] = demoflags = 0; - ping = 9999; - team = TEAM_VOID; - skin = 0; + bottomRTT = ping = 9999; + team = TEAM_SPECT; + loopi(2) skin[i] = 0; position.setsizenodelete(0); messages.setsizenodelete(0); isauthed = haswelcome = false; @@ -256,6 +265,7 @@ struct client // server side version of "dynent" type saychars = 0; lastforce = 0; spawnindex = -1; + spectcn = FPCN_VOID; mapchange(); } @@ -282,7 +292,7 @@ struct worldstate struct server_entity // server side version of "entity" type { int type; - bool spawned, hascoord; + bool spawned, legalpickup; int spawntime; short x, y; }; @@ -298,20 +308,23 @@ struct demofile void startgame(const char *newname, int newmode, int newtime = -1, bool notify = true); void disconnect_client(int n, int reason = -1); int clienthasflag(int cn); -bool refillteams(bool now = false, bool notify = true); +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); +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); +void senddisconnectedscores(int cn); void process(ENetPacket *packet, int sender, int chan); -void welcomepacket(ucharbuf &p, int n, ENetPacket *packet, bool forcedeath = false); -void sendwelcome(client *cl, int chan = 1, bool forcedeath = false); +void welcomepacket(ucharbuf &p, int n, ENetPacket *packet); +void sendwelcome(client *cl, int chan = 1); int numclients(); +bool updateclientteam(int cln, int newteam, int ftr); +void forcedeath(client *cl); +void sendf(int cn, int chan, const char *format, ...); extern bool isdedicated; extern string smapname; -extern int smode, servmillis; extern mapstats smapstats; extern char *maplayout; diff --git a/source/src/serveractions.h b/source/src/serveractions.h index 5d0cf379..dd82cdfe 100644 --- a/source/src/serveractions.h +++ b/source/src/serveractions.h @@ -47,7 +47,8 @@ struct mapaction : serveraction if(isdedicated) { bool notify = valid_client(caller); - mapstats *ms = map[0] ? getservermapstats(map) : NULL; + int maploc = MAP_NOTFOUND; + mapstats *ms = map[0] ? getservermapstats(map, false, &maploc) : NULL; mapok = ms != NULL; if(!mapok) { @@ -56,7 +57,7 @@ struct mapaction : serveraction else { // check, if map supports mode if(mode == GMODE_COOPEDIT && !strchr(scl.voteperm, 'e')) role = CR_ADMIN; - bool romap = mode == GMODE_COOPEDIT && readonlymap(findmappath(map)); + bool romap = mode == GMODE_COOPEDIT && readonlymap(maploc); int smode = mode; // 'borrow' the mode macros by replacing a global by a local var bool spawns = (m_teammode && !m_ktf) ? ms->hasteamspawns : ms->hasffaspawns; bool flags = m_flags && !m_htf ? ms->hasflags : true; @@ -98,14 +99,14 @@ struct mapaction : serveraction struct demoplayaction : serveraction { - char *map; - void perform() { startgame(map, GMODE_DEMO); } - demoplayaction(char *map) : map(map) + char *demofilename; + void perform() { startdemoplayback(demofilename); } + demoplayaction(char *demofilename) : demofilename(demofilename) { area = EE_LOCAL_SERV; // only local } - ~demoplayaction() { DELETEA(map); } + ~demoplayaction() { DELETEA(demofilename); } }; struct playeraction : serveraction @@ -126,7 +127,7 @@ struct playeraction : serveraction struct forceteamaction : playeraction { - void perform() { forceteam(cn, team_opposite(clients[cn]->team), true); } + void perform() { updateclientteam(cn, team_opposite(clients[cn]->team), FTR_SILENTFORCE); } virtual bool isvalid() { return m_teammode && valid_client(cn); } forceteamaction(int cn, int caller) : playeraction(cn) { @@ -197,7 +198,7 @@ struct removebansaction : serveraction struct mastermodeaction : serveraction { int mode; - void perform() { mastermode = mode; } + void perform() { changemastermode(mode); } bool isvalid() { return mode >= 0 && mode < MM_NUM; } mastermodeaction(int mode) : mode(mode) { @@ -216,7 +217,8 @@ struct autoteamaction : enableaction { void perform() { - sendf(-1, 1, "ri2", SV_AUTOTEAM, (autoteam = enable) == 1 ? AT_ENABLED : AT_DISABLED); + autoteam = enable; + sendservermode(); if(m_teammode && enable) refillteams(true); } autoteamaction(bool enable) : enableaction(enable) @@ -230,7 +232,7 @@ struct shuffleteamaction : serveraction { void perform() { - sendf(-1, 1, "ri2", SV_AUTOTEAM, AT_SHUFFLE); + sendf(-1, 1, "ri2", SV_SERVERMODE, sendservermode(false) | AT_SHUFFLE); shuffleteams(); } bool isvalid() { return serveraction::isvalid() && m_teammode; } @@ -241,10 +243,10 @@ struct shuffleteamaction : serveraction } }; -struct recorddemoaction : enableaction +struct recorddemoaction : enableaction // TODO: remove completely { - void perform() { demonextmatch = enable; } - bool isvalid() { return serveraction::isvalid() && !scl.demoeverymatch; } + void perform() { } + bool isvalid() { return serveraction::isvalid(); } recorddemoaction(bool enable) : enableaction(enable) { role = roleconf('R'); @@ -254,17 +256,16 @@ struct recorddemoaction : enableaction struct stopdemoaction : serveraction { - bool isvalid() { return serveraction::isvalid() && (m_demo || !scl.demoeverymatch); } + bool isvalid() { return serveraction::isvalid() && m_demo; } void perform() { if(m_demo) enddemoplayback(); - else enddemorecord(); } stopdemoaction() { role = CR_ADMIN; area |= EE_LOCAL_SERV; - s_strcpy(desc, "stop demo"); + s_strcpy(desc, "stop demo playback"); } }; diff --git a/source/src/serverbrowser.cpp b/source/src/serverbrowser.cpp index 96587b9c..2a375c04 100644 --- a/source/src/serverbrowser.cpp +++ b/source/src/serverbrowser.cpp @@ -366,14 +366,14 @@ void pingservers(bool issearch, serverinfo *onlyconnected) { serverinfo *si = onlyconnected; //p.len = baselen; - if(si->getinfo) + if(si->getinfo == EXTPING_SERVERINFO) { putint(p, EXTPING_SERVERINFO); const char *lang = getalias("LANG"); if(!lang || strlen(lang) != 2) lang = "en"; loopi(2) putint(p, lang[i]); } - else putint(p, si->getnames || issearch ? EXTPING_NAMELIST : EXTPING_NOP); + else putint(p, issearch ? EXTPING_NAMELIST : (totalmillis - si->uplinkqual_age > 60 * 1000 ? EXTPING_UPLINKSTATS : si->getinfo)); buf.data = ping; buf.dataLength = p.length(); enet_socket_send(pingsock, &si->address, &buf, 1); @@ -388,7 +388,7 @@ void pingservers(bool issearch, serverinfo *onlyconnected) if(++lastping >= servers.length()) lastping = 0; if(si.address.host == ENET_HOST_ANY) continue; p.len = baselen; - putint(p, si.getnames || issearch ? EXTPING_NAMELIST : EXTPING_NOP); + putint(p, issearch ? EXTPING_NAMELIST : (totalmillis - si.uplinkqual_age > 60 * 1000 ? EXTPING_UPLINKSTATS : si.getinfo)); buf.data = ping; buf.dataLength = p.length(); enet_socket_send(pingsock, &si.address, &buf, 1); @@ -400,7 +400,7 @@ void pingservers(bool issearch, serverinfo *onlyconnected) address.host = ENET_HOST_BROADCAST; address.port = CUBE_SERVINFO_PORT_LAN; p.len = baselen; - putint(p, issearch ? EXTPING_NAMELIST : EXTPING_NOP); + putint(p, issearch ? EXTPING_NAMELIST : EXTPING_UPLINKSTATS); buf.data = ping; buf.dataLength = p.length(); enet_socket_send(pingsock, &address, &buf, 1); @@ -495,6 +495,10 @@ void checkpings() { switch(query) { + #define RESETINFOLINES() si->infotexts.setsizenodelete(0); \ + ucharbuf q(si->textdata, sizeof(si->textdata)) + #define ADDINFOLINE(msg) si->infotexts.add((char *)si->textdata + q.length()); \ + sendstring(msg, q) case EXTPING_NAMELIST: { si->playernames.setsizenodelete(0); @@ -514,14 +518,11 @@ void checkpings() } case EXTPING_SERVERINFO: { - si->infotexts.setsizenodelete(0); - ucharbuf q(si->textdata, sizeof(si->textdata)); + RESETINFOLINES(); getstring(text, p); - si->getinfo = 0; if(strlen(text) != 2) { - si->infotexts.add((char *)si->textdata); - sendstring("this server does not provide additional information", q); + ADDINFOLINE("this server does not provide additional information"); break; } strcpy(si->lang, text); @@ -532,8 +533,7 @@ void checkpings() { text[MAXINFOLINELEN] = '\0'; cutcolorstring(text, 80); - si->infotexts.add((char *)si->textdata + q.length()); - sendstring(strcmp(text, ".") ? text : "", q); + ADDINFOLINE(strcmp(text, ".") ? text : ""); } else break; } @@ -541,24 +541,75 @@ void checkpings() } case EXTPING_MAPROT: { - si->infotexts.setsizenodelete(0); - ucharbuf q(si->textdata, sizeof(si->textdata)); + RESETINFOLINES(); int n = getint(p); - si->getinfo = 0; + ADDINFOLINE("\f1server map rotation:"); + ADDINFOLINE(""); while(p.remaining()) { getstring(text, p); + filtertext(text, text, 0); if(*text && !p.overread()) { text[MAXINFOLINELEN] = '\0'; loopi(n) s_strcatf(text, ", %d", getint(p)); - si->infotexts.add((char *)si->textdata + q.length()); - sendstring(text , q); + ADDINFOLINE(text ); } else break; } break; } + case EXTPING_UPLINKSTATS: + { + if(si->maxclients > 3) + { + si->uplinkqual_age = totalmillis; + int maxs = 0, maxc = 0, ts, tc; + loopi(si->maxclients - 3) + { + ts = tc = si->uplinkstats[i + 4] = p.get(); + if(si->maxclients < 8 || i > 2) + { + ts &= 0xF0; tc &= 0x0F; + if(ts > maxs) maxs = ts; + if(ts > 0x40 && tc > maxc) maxc = tc; // spent time = 2 ^ ((ts >> 4) - 1) * 30 sec, so 0x50 is 8..15 minutes + } + } + if(maxs < 0x90) maxc -= 2; // go easy on fresh started servers + if(maxs < 0x50) si->uplinkqual = 3; + else if(maxc < 3) si->uplinkqual = 5; // choke_percentage = 1 / 2 ^ (15 - tc), so 0x03 is 0.02% + else if(maxc < 6) si->uplinkqual = 4; // 0.2% + else if(maxc == 6) si->uplinkqual = 3; + else if(maxc < 9) si->uplinkqual = 2; // 1.6% (yep, that IS bad) + else si->uplinkqual = 1; + } + if(si->getinfo == EXTPING_UPLINKSTATS) + { + RESETINFOLINES(); + if(si->maxclients < 4) + { + ADDINFOLINE("server is too small for uplink quality statistics"); + } + else + { + ADDINFOLINE("\f1server uplink quality and usage statistics:"); + ADDINFOLINE(""); + ADDINFOLINE("players:\terrors/time"); + for(int i = 4; i <= si->maxclients; i++) + { + s_sprintfd(msg)(" %d\t", i); + loopj(15) s_strcatf(msg, "\a%c ", '0' + ((si->uplinkstats[i] & 0x0F) > j) + 2 * ((si->uplinkstats[i] & 0xF0) > (j << 4))); + s_strcatf(msg, "\t\t [%02X]", si->uplinkstats[i]); + ADDINFOLINE(msg); + } + ADDINFOLINE(""); + ADDINFOLINE("\f4red bar: error rate, green bar: time played"); + } + } + break; + } + #undef RESETINFOLINES + #undef ADDINFOLINE } } } @@ -566,6 +617,7 @@ void checkpings() { si->pongflags = 0; } + if(si->getinfo == query) si->getinfo = EXTPING_NOP; if(si->pongflags > 0) { const char *sp = ""; @@ -696,10 +748,11 @@ bool matchplayername(const char *name) VARP(serverbrowserhideip, 0, 0, 2); VARP(serverbrowserhidefavtag, 0, 1, 2); VAR(showweights, 0, 0, 1); +VARP(hidefavicons, 0, 0, 1); vector favcats; -const char *fc_als[] = { "weight", "tag", "desc", "red", "green", "blue", "alpha", "keys", "ignore" }; -enum { FC_WEIGHT = 0, FC_TAG, FC_DESC, FC_RED, FC_GREEN, FC_BLUE, FC_ALPHA, FC_KEYS, FC_IGNORE, FC_NUM }; +const char *fc_als[] = { "weight", "tag", "desc", "red", "green", "blue", "alpha", "keys", "ignore", "image" }; +enum { FC_WEIGHT = 0, FC_TAG, FC_DESC, FC_RED, FC_GREEN, FC_BLUE, FC_ALPHA, FC_KEYS, FC_IGNORE, FC_IMAGE, FC_NUM }; VARF(showonlyfavourites, 0, 0, 100, { @@ -839,6 +892,7 @@ bool assignserverfavourites() loopv(servers) { serverinfo &si = *servers[i]; + if(si.address.host == ENET_HOST_ANY || si.ping == 9999 || si.protocol != PROTOCOL_VERSION) continue; if((!alxn[FC_IGNORE] || showonlythiscat) && favcatcheckkey(si, k)) { si.weight += alxn[FC_WEIGHT]; @@ -868,12 +922,14 @@ bool assignserverfavourites() COMMAND(addfavcategory, ARG_1STR); COMMAND(listfavcats, ARG_NONE); +static serverinfo *lastselectedserver = NULL; +static bool pinglastselected = false; + void refreshservers(void *menu, bool init) { static int servermenumillis; static bool usedselect = false; static string title; - static serverinfo *lastselectedserver = NULL; bool issearch = menu == searchmenu; bool isinfo = menu == serverinfomenu; bool isscoreboard = menu == NULL; @@ -900,7 +956,7 @@ void refreshservers(void *menu, bool init) loopv(servers) if(lastselectedserver == servers[i]) { found = true; break; } if(!found) lastselectedserver = NULL; } - menutitle(menu, "extended server information"); + menutitle(menu, "extended server information (F5: refresh)"); menureset(menu); static string infotext; static char dummy = '\0'; @@ -910,16 +966,17 @@ void refreshservers(void *menu, bool init) s_sprintf(si.full)("%s:%d %s", si.name, si.port, si.sdesc); menumanual(menu, si.full); menumanual(menu, &dummy); - if(si.infotexts.length()) + if(si.infotexts.length() && !pinglastselected) { infotext[0] = '\0'; - loopv(si.infotexts) menumanual(menu, si.infotexts[i]); + loopv(si.infotexts) menuimagemanual(menu, NULL, "bargraphs", si.infotexts[i]); } else { s_strcpy(infotext, "-- waiting for server response --"); - si.getinfo = 1; - if(init || totalmillis - lastinfo >= servpingrate) pingservers(false, lastselectedserver); + if(si.getinfo == EXTPING_NOP) si.getinfo = EXTPING_SERVERINFO; + if(init || pinglastselected || totalmillis - lastinfo >= servpingrate) pingservers(false, lastselectedserver); + pinglastselected = false; } } else @@ -973,9 +1030,10 @@ void refreshservers(void *menu, bool init) bool showthisone = !(banned && showonlygoodservers) && !(showonlyfavourites > 0 && si.favcat != showonlyfavourites - 1); bool serverfull = si.numplayers >= si.maxclients; bool needspasswd = (si.pongflags & (1 << PONGFLAG_PASSWORD)) > 0; - bool isprivate = (si.pongflags >> PONGFLAG_MASTERMODE) > 0; + int mmode = (si.pongflags >> PONGFLAG_MASTERMODE) & MM_MASK; char basecolor = banned ? '4' : (curserver == servers[i] ? '1' : '5'); - char plnumcolor = serverfull ? '2' : (needspasswd ? '3' : (isprivate ? '1' : basecolor)); + char plnumcolor = serverfull ? '2' : (needspasswd ? '3' : (mmode != MM_OPEN ? '1' : basecolor)); + const char *favimage = NULL; if(si.address.host != ENET_HOST_ANY && si.ping != 9999) { if(si.protocol!=PROTOCOL_VERSION) @@ -985,10 +1043,11 @@ void refreshservers(void *menu, bool init) } else { - filterrichtext(text, si.favcat > -1 ? favcattags[si.favcat] : ""); + if(!hidefavicons && showfavtag && si.favcat > -1) favimage = getalias(favcatargname(favcats[si.favcat], FC_IMAGE)); + filterrichtext(text, si.favcat > -1 && !favimage ? favcattags[si.favcat] : ""); if(showweights) s_strcatf(text, "(%d)", si.weight); - s_sprintf(si.full)(showfavtag ? "\fs%s\fr\t" : "", text); - s_strcatf(si.full, "\fs\f%c%d\t\fs\f%c%d/%d\fr\t", basecolor, si.ping, plnumcolor, si.numplayers, si.maxclients); + s_sprintf(si.full)(showfavtag ? (favimage ? "\t" : "\fs%s\fr\t") : "", text); + s_strcatf(si.full, "\fs\f%c%d\t\fs\f%c%d/%d\fr\t\a%c ", basecolor, si.ping, plnumcolor, si.numplayers, si.maxclients, '0' + si.uplinkqual); if(si.map[0]) { s_strcatf(si.full, "%s, %s", si.map, modestr(si.mode, modeacronyms > 0)); @@ -1019,15 +1078,15 @@ void refreshservers(void *menu, bool init) filtertext(text, si.sdesc); for(char *p = text; (p = strchr(p, '\"')); *p++ = ' '); text[30] = '\0'; - s_sprintf(si.cmd)("sbconnect %s %d %d %d %d %d \"%s\"", si.name, si.port, serverfull ?1:0, needspasswd ?1:0, isprivate ?1:0, banned, text); + s_sprintf(si.cmd)("sbconnect %s %d %d %d %d %d \"%s\"", si.name, si.port, serverfull ?1:0, needspasswd ?1:0, mmode, banned, text); } else s_sprintf(si.cmd)("connect %s %d", si.name, si.port); - menumanual(menu, si.full, si.cmd, si.bgcolor, si.description); + menuimagemanual(menu, favimage, "serverquality", si.full, si.cmd, si.bgcolor, si.description); if(!issearch && servers[i] == oldsel) { ((gmenu *)menu)->menusel = ((gmenu *)menu)->items.length() - 1; usedselect = true; - si.getnames = shownamesinbrowser ? 1 : 0; + if(shownamesinbrowser) si.getinfo = EXTPING_NAMELIST; } if((shownamesinbrowser && servers[i] == oldsel && si.playernames.length()) || issearch) { @@ -1166,6 +1225,37 @@ bool serverskey(void *menu, int code, bool isdown, int unicode) return false; } +bool serverinfokey(void *menu, int code, bool isdown, int unicode) +{ + if(!isdown) return false; + switch(code) + { + case SDLK_HOME: + if(menu) ((gmenu *)menu)->menusel = 0; + return true; + + case SDLK_F5: + if(lastselectedserver) lastselectedserver->getinfo = EXTPING_SERVERINFO; + pinglastselected = true; + return true; + + case SDLK_F1: + showmenu("serverinfo help"); // feel free to actually write this menu ;) + return true; + + case SDLK_F2: // yep: undocumented :) + if(lastselectedserver) lastselectedserver->getinfo = EXTPING_UPLINKSTATS; + pinglastselected = true; + break; + + case SDLK_m: + if(lastselectedserver) lastselectedserver->getinfo = EXTPING_MAPROT; + pinglastselected = true; + break; + } + return false; +} + void clearservers() { resolverclear(); diff --git a/source/src/serverevents.h b/source/src/serverevents.h index e7b0a8dd..09f9da2a 100644 --- a/source/src/serverevents.h +++ b/source/src/serverevents.h @@ -127,8 +127,7 @@ void processevent(client *c, reloadevent &e) void processevent(client *c, akimboevent &e) { clientstate &gs = c->state; - if(!gs.isalive(gamemillis) || gs.akimbos<=0) return; - gs.akimbos--; + if(!gs.isalive(gamemillis) || gs.akimbomillis) return; gs.akimbomillis = e.millis+30000; } @@ -145,6 +144,7 @@ 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; } while(c->events.length()) { gameevent &e = c->events[0]; diff --git a/source/src/serverfiles.h b/source/src/serverfiles.h index 7b2be74a..e86fea42 100644 --- a/source/src/serverfiles.h +++ b/source/src/serverfiles.h @@ -138,8 +138,10 @@ struct servermapbuffer // sending of maps between clients // provide maps by the server -enum { MAP_NOTFOUND = 0, MAP_TEMP, MAP_CUSTOM, MAP_LOCAL, MAP_OFFICIAL }; -#define readonlymap(x) ((x) >= MAP_CUSTOM) // eval x only once! +enum { MAP_NOTFOUND = 0, MAP_TEMP, MAP_CUSTOM, MAP_LOCAL, MAP_OFFICIAL, MAP_VOID }; +const char *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) { @@ -175,33 +177,16 @@ int findmappath(const char *mapname, char *filename) return loc; } -mapstats *getservermapstats(const char *mapname, bool getlayout) +mapstats *getservermapstats(const char *mapname, bool getlayout, int *maploc) { string filename; - int loc = findmappath(mapname, filename); + int ml; + if(!maploc) maploc = &ml; + *maploc = findmappath(mapname, filename); if(getlayout) DELETEA(maplayout); - return loc == MAP_NOTFOUND ? NULL : loadmapstats(filename, getlayout); + return *maploc == MAP_NOTFOUND ? NULL : loadmapstats(filename, getlayout); } - - - - - - - - - - - - - - - - - - - // server config files @@ -330,6 +315,18 @@ struct servermaprot : serverconfigfile return ccs; } + void restart(bool notify = true) // restart current map + { +#ifndef STANDALONE + if(!isdedicated) + { + startgame(getclientmap(), getclientmode(), -1, notify); + return; + } +#endif + startgame(smapname, smode, -1, notify); + } + configset *current() { return configsets.inrange(curcfgset) ? &configsets[curcfgset] : NULL; } }; diff --git a/source/src/serverms.cpp b/source/src/serverms.cpp index da54e95f..2deb6f82 100644 --- a/source/src/serverms.cpp +++ b/source/src/serverms.cpp @@ -228,6 +228,13 @@ void serverms(int mode, int numplayers, int minremain, char *smapname, int milli extping_maprot(po); break; } + case EXTPING_UPLINKSTATS: + { + extern void extping_uplinkstats(ucharbuf &po); + putint(po, query); + extping_uplinkstats(po); + break; + } case EXTPING_NOP: default: putint(po, EXTPING_NOP); diff --git a/source/src/tools.cpp b/source/src/tools.cpp index e628d463..765e07d5 100644 --- a/source/src/tools.cpp +++ b/source/src/tools.cpp @@ -172,14 +172,18 @@ void sethomedir(const char *dir) } #endif - printf("Using home directory: %s\n", tmpdir); +#ifndef STANDALONE + clientlogf("Using home directory: %s", tmpdir); +#endif fixdir(s_strcpy(homedir, tmpdir)); createdir(homedir); } void addpackagedir(const char *dir) { - printf("Adding package directory: %s\n", dir); +#ifndef STANDALONE + clientlogf("Adding package directory: %s", dir); +#endif fixdir(packagedirs.add(newstringbuf(dir))); } diff --git a/source/src/weapon.cpp b/source/src/weapon.cpp index c72adb43..cd8c4658 100644 --- a/source/src/weapon.cpp +++ b/source/src/weapon.cpp @@ -583,16 +583,21 @@ bool weapon::reload() return true; } +VAR(oldfashionedgunstats, 0, 0, 1); + void weapon::renderstats() { char gunstats[64]; - sprintf(gunstats, "%i", mag); ///%i", mag, ammo); + sprintf(gunstats, oldfashionedgunstats ? "%i/%i" : "%i", mag, ammo); draw_text(gunstats, 690, 823); - int offset = text_width(gunstats); - glScalef(0.5f, 0.5f, 1.0f); - sprintf(gunstats, "%i", ammo); - draw_text(gunstats, (690 + offset)*2, 826*2); - glLoadIdentity(); + if(!oldfashionedgunstats) + { + int offset = text_width(gunstats); + glScalef(0.5f, 0.5f, 1.0f); + sprintf(gunstats, "%i", ammo); + draw_text(gunstats, (690 + offset)*2, 826*2); + glLoadIdentity(); + } } //VAR(recoiltest, 0, 0, 1); // FIXME ON RELEASE @@ -808,7 +813,7 @@ bool grenades::attack(vec &targ) int attackmillis = lastmillis-owner->lastaction; vec &to = targ; - bool waitdone = attackmillis>=gunwait; + bool waitdone = attackmillis>=gunwait && !(m_arena && m_teammode && arenaintermission); if(waitdone) gunwait = reloading = 0; switch(state) diff --git a/source/src/wizard.cpp b/source/src/wizard.cpp index 8c7d049d..727066be 100644 --- a/source/src/wizard.cpp +++ b/source/src/wizard.cpp @@ -35,38 +35,32 @@ int wizardmain(int argc, char **argv) map args; cout << "AssaultCube Server Wizard" << endl << endl; - cout << "You can now specify some optional server settings. The default settings will be used if you decide to leave the fields empty. See README.html for a description of the settings." << endl << endl; + cout << "You can now specify some optional server settings. " + "The default settings will be used if you decide to leave the fields empty. " + "If you're not sure: leave the field empty. " + "See README.html for a description of the settings." << endl << endl; cout << "server description:\t"; getline(cin, args["n"]); + cout << "message of the day:\t"; + getline(cin, args["o"]); + cout << "max clients:\t\t"; getline(cin, args["c"]); - cout << "password:\t\t"; - getline(cin, args["p"]); - cout << "admin password:\t\t"; getline(cin, args["x"]); - cout << "message of the day:\t"; - getline(cin, args["o"]); + cout << "player password:\t\t"; + getline(cin, args["p"]); cout << "server port:\t\t"; getline(cin, args["f"]); - cout << "masterserver:\t\t"; - getline(cin, args["m"]); - - cout << "maprotation file:\t"; - getline(cin, args["r"]); - - cout << "score threshold:\t"; - getline(cin, args["k"]); - - string permdemo; - cout << "demorecord buffer size:\t"; - getline(cin, permdemo); + string ispub; + cout << "public server (Yes|no):\t"; + getline(cin, ispub); string cmds; cout << "additional commandline parameters:\t"; @@ -98,7 +92,7 @@ int wizardmain(int argc, char **argv) else argstr += '"' + (*i).second + '"'; // escape spaces } } - if(permdemo.empty() || atoi(permdemo.c_str())) argstr += " -D" + permdemo; + if(toupper(*(ispub.c_str())) == 'N') argstr += " -mlocalhost"; if(!cmds.empty()) argstr += " " + cmds; cout << endl << "Writing your configuration to " << outfile << " ... "; diff --git a/source/src/world.cpp b/source/src/world.cpp index f54f0983..60123c87 100644 --- a/source/src/world.cpp +++ b/source/src/world.cpp @@ -61,7 +61,7 @@ void remip(const block &b, int level) } r->floor = floor; r->ceil = ceil; - } + } if(r->type==CORNER) goto mip; // special case: don't ever split even if textures etc are different r->defer = 1; if(SOLID(r)) @@ -246,9 +246,9 @@ entity *newentity(int index, int x, int y, int z, char *what, int v1, int v2, in case LIGHT: if(v1>64) e.attr1 = 64; if(!v1) e.attr1 = 16; - if(!v2 && !v3 && !v4) e.attr2 = 255; + if(!v2 && !v3 && !v4) e.attr2 = 255; break; - + case MAPMODEL: e.attr4 = e.attr3; e.attr3 = e.attr2; @@ -283,7 +283,7 @@ entity *newentity(int index, int x, int y, int z, char *what, int v1, int v2, in void entset(char *what, char *a1, char *a2, char *a3, char *a4) { int n = closestent(); - if(n>=0) + if(n>=0) { entity &e = ents[n]; newentity(n, e.x, e.y, e.z, what, ATOI(a1), ATOI(a2), ATOI(a3), ATOI(a4)); @@ -291,9 +291,9 @@ void entset(char *what, char *a1, char *a2, char *a3, char *a4) } COMMAND(entset, ARG_5STR); - + void clearents(char *name) -{ +{ int type = findtype(name); if(noteditmode() || multiplayer()) return; loopv(ents) @@ -384,46 +384,77 @@ void setupworld(int factor) loopi(LARGEST_FACTOR*2) { wmip[i] = w; w += cubicsize>>(i*2); } } -bool empty_world(int factor, bool force) // main empty world creation routine, if passed factor -1 will enlarge old world by 1 +sqr *sqrdefault(sqr *s) { - if(!force && noteditmode()) return false; - sqr *oldworld = world; + if(!s) return s; + s->r = s->g = s->b = 150; + s->ftex = DEFAULT_FLOOR; + s->ctex = DEFAULT_CEIL; + s->wtex = s->utex = DEFAULT_WALL; + s->type = SOLID; + s->floor = 0; + s->ceil = 16; + s->vdelta = 0; + s->defer = 0; + return s; +} + +bool worldbordercheck(int x1, int x2, int y1, int y2, int z1, int z2) // check for solid world border +{ + loop(x, ssize) loop(y, ssize) + { + if(x >= x1 && x < ssize - x2 && y >= y1 && y < ssize - y2) + { + if(S(x,y)->type != SOLID && (S(x,y)->ceil > 126 - z1 || S(x,y)->floor < -127 + z2)) return false; + } + else + { + if(S(x,y)->type != SOLID) return false; + } + } + return true; +} + +bool empty_world(int factor, bool force) // main empty world creation routine, if passed factor -1 will enlarge old world by 1, factor -2 will shrink old world by 1 +{ + if(!force && noteditmode()) return false; + if(factor == -2 && !worldbordercheck(ssize/4 + MINBORD, ssize/4 + MINBORD, ssize/4 + MINBORD, ssize/4 + MINBORD, 0, 0)) { conoutf("map does not fit into smaller world"); return false; } + block *ow = NULL, be = { 0, 0, ssize, ssize }, bs = { ssize/4, ssize/4, ssize/2, ssize/2 }; + int oldfactor = sfactor; bool copy = false; - if(oldworld && factor<0) { factor = sfactor+1; copy = true; } + if(world && factor<0) { factor = sfactor + (factor == -2 ? -1 : 1); copy = true; } if(factorLARGEST_FACTOR) factor = LARGEST_FACTOR; + if(copy && oldfactor == factor) return false; + bool shrink = factor < oldfactor; + bool clearmap = !copy && world; + if(copy) ow = blockcopy(shrink ? bs : be); + + DELETEA(world); setupworld(factor); - loop(x,ssize) loop(y,ssize) { - sqr *s = S(x,y); - s->r = s->g = s->b = 150; - s->ftex = DEFAULT_FLOOR; - s->ctex = DEFAULT_CEIL; - s->wtex = s->utex = DEFAULT_WALL; - s->type = SOLID; - s->floor = 0; - s->ceil = 16; - s->vdelta = 0; - s->defer = 0; + sqrdefault(S(x,y)); } - + strncpy(hdr.head, "ACMP", 4); hdr.version = MAPVERSION; hdr.headersize = sizeof(header); hdr.sfactor = sfactor; - if(copy) { - loop(x,ssize/2) loop(y,ssize/2) - { - *S(x+ssize/4, y+ssize/4) = *SWS(oldworld, x, y, sfactor-1); - } + ow->x = ow->y = shrink ? 0 : ssize/4; + blockpaste(*ow); + int moveents = shrink ? -ssize/2 : ssize/4; loopv(ents) { - ents[i].x += ssize/4; - ents[i].y += ssize/4; + ents[i].x += moveents; + ents[i].y += moveents; + if(OUTBORD(ents[i].x, ents[i].y)) ents[i].type = NOTUSED; } + player1->o.x += moveents; + player1->o.y += moveents; + entinmap(player1); } else { @@ -433,39 +464,38 @@ bool empty_world(int factor, bool force) // main empty world creation routine loopi(sizeof(hdr.reserved)/sizeof(hdr.reserved[0])) hdr.reserved[i] = 0; loopk(3) loopi(256) hdr.texlists[k][i] = i; ents.setsize(0); - block b = { 8, 8, ssize-16, ssize-16 }; + block b = { 8, 8, ssize-16, ssize-16 }; edittypexy(SPACE, b); } - + calclight(); - if(factor>=0) resetmap(); - if(oldworld) + resetmap(!copy); + if(clearmap) { - delete[] oldworld; - if(factor>=0) - { - //toggleedit(); - pushscontext(IEXC_MAPCFG); - persistidents = false; - execfile("config/default_map_settings.cfg"); - persistidents = true; - popscontext(); - setvar("fullbright", 1); - fullbrightlight(); - } + pushscontext(IEXC_MAPCFG); + persistidents = false; + execfile("config/default_map_settings.cfg"); + persistidents = true; + popscontext(); + setvar("fullbright", 1); + fullbrightlight(); } - if(factor>=0) + if(!copy) { findplayerstart(player1, true); startmap("", false); } + else conoutf("new map size: %d", sfactor); + freeblock(ow); return true; } void mapenlarge() { if(empty_world(-1, false)) addmsg(SV_NEWMAP, "ri", -1); } +void mapshrink() { if(empty_world(-2, false)) addmsg(SV_NEWMAP, "ri", -2); } void newmap(int i) { if(empty_world(i, false)) addmsg(SV_NEWMAP, "ri", max(i, 0)); } COMMAND(mapenlarge, ARG_NONE); +COMMAND(mapshrink, ARG_NONE); COMMAND(newmap, ARG_1INT); COMMANDN(recalc, calclight, ARG_NONE); COMMAND(delent, ARG_NONE); diff --git a/source/src/worldlight.cpp b/source/src/worldlight.cpp index 13502677..de23b6d0 100644 --- a/source/src/worldlight.cpp +++ b/source/src/worldlight.cpp @@ -10,15 +10,15 @@ void lightray(float bx, float by, const persistent_entity &light, float fade = 1 float lx = light.x+(flicker ? (rnd(21)-10)*0.1f : 0); float ly = light.y+(flicker ? (rnd(21)-10)*0.1f : 0); float dx = bx-lx; - float dy = by-ly; + float dy = by-ly; float dist = (float)sqrt(dx*dx+dy*dy); if(dist<1.0f) return; int reach = light.attr1; int steps = (int)(reach*reach*1.6f/dist); // can change this for speedup/quality? const int PRECBITS = 12; const float PRECF = 4096.0f; - int x = (int)(lx*PRECF); - int y = (int)(ly*PRECF); + int x = (int)(lx*PRECF); + int y = (int)(ly*PRECF); int fadescale = (int)(fade*PRECF); int l = light.attr2*fadescale; int stepx = (int)(dx/(float)steps*PRECF); @@ -34,7 +34,7 @@ void lightray(float bx, float by, const persistent_entity &light, float fade = 1 { if(flicker) { - int dimness = rnd((((255<>(PRECBITS+4))+1); + int dimness = rnd((((255<>(PRECBITS+4))+1); x += stepx*dimness; y += stepy*dimness; } @@ -51,7 +51,7 @@ void lightray(float bx, float by, const persistent_entity &light, float fade = 1 stepb /= lightscale; loopi(steps) { - sqr *s = S(x>>PRECBITS, y>>PRECBITS); + sqr *s = S(x>>PRECBITS, y>>PRECBITS); int tl = (l>>PRECBITS)+s->r; s->r = tl>255 ? 255 : tl; tl = (g>>PRECBITS)+s->g; @@ -73,7 +73,7 @@ void lightray(float bx, float by, const persistent_entity &light, float fade = 1 { if(flicker) { - int dimness = rnd((((255<>(PRECBITS+4))+1); + int dimness = rnd((((255<>(PRECBITS+4))+1); x += stepx*dimness; y += stepy*dimness; } @@ -82,9 +82,9 @@ void lightray(float bx, float by, const persistent_entity &light, float fade = 1 loopi(steps) { - sqr *s = S(x>>PRECBITS, y>>PRECBITS); + sqr *s = S(x>>PRECBITS, y>>PRECBITS); int tl = (l>>PRECBITS)+s->r; - s->r = s->g = s->b = tl>255 ? 255 : tl; + s->r = s->g = s->b = tl>255 ? 255 : tl; if(SOLID(s)) return; x += stepx; y += stepy; @@ -97,7 +97,7 @@ void lightray(float bx, float by, const persistent_entity &light, float fade = 1 { loopi(steps) { - sqr *s = S(x>>PRECBITS, y>>PRECBITS); + sqr *s = S(x>>PRECBITS, y>>PRECBITS); int light = l>>PRECBITS; if(light>s->r) s->r = s->g = s->b = (uchar)light; if(SOLID(s)) return; @@ -106,7 +106,7 @@ void lightray(float bx, float by, const persistent_entity &light, float fade = 1 l -= stepl; } } - + } void calclightsource(const persistent_entity &l, float fade = 1, bool flicker = true) @@ -116,7 +116,7 @@ void calclightsource(const persistent_entity &l, float fade = 1, bool flicker = int ex = l.x+reach; int sy = l.y-reach; int ey = l.y+reach; - + const float s = 0.8f; for(float sx2 = (float)sx; sx2<=ex; sx2+=s*2) { lightray(sx2, (float)sy, l, fade, flicker); lightray(sx2, (float)ey, l, fade, flicker); } @@ -165,7 +165,7 @@ void calclight() entity &e = ents[i]; if(e.type==LIGHT) calclightsource(e); } - + block b = { 1, 1, ssize-2, ssize-2 }; postlightarea(b); setvar("fullbright", 0); @@ -207,7 +207,7 @@ void preparedynlight(dlight &d) if(area.x+area.xs>ssize-2) area.xs = ssize-2-area.x; if(area.y+area.ys>ssize-2) area.ys = ssize-2-area.y; - if(d.area) + if(d.area) { if(insidearea(*d.area, area)) return; @@ -288,7 +288,7 @@ void dodynlights() } area = d.area; } - if(area) postlightarea(*area); + if(area) postlightarea(*area); lastcalclight = totalmillis; } @@ -296,7 +296,7 @@ void undodynlights() { if(dlights.empty()) return; const block *area = NULL; - loopvrev(dlights) + loopvrev(dlights) { const dlight &d = dlights[i]; if(area)