Compare commits

..

60 Commits

Author SHA1 Message Date
Alibek Omarov
03a9d08053 wip [skip ci] 2025-01-16 03:45:49 +03:00
Alibek Omarov
a75666a3cd engine: common: fix dedicated build by defining SV_GetMaxClients before CL_GetMaxClients stub (that redirects to it)
Also remove CL_Shutdown from dedicated stub lists.
2025-01-14 13:10:02 +03:00
Alibek Omarov
49426721d8 engine: use zero initializers instead of memset 2025-01-14 12:11:27 +03:00
Alibek Omarov
04125284a3 engine: dedicated: make some stubs inlined 2025-01-14 12:10:31 +03:00
Alibek Omarov
1d4f7b2f94 engine: server: slight refactoring, make SV_ModelHandle inlined, move PlayerIsFrozen to sv_client, fix packet loss data type 2025-01-14 12:09:23 +03:00
Alibek Omarov
38c82a3f76 engine: don't bother calculating crc32 for local clients for better performance (thanks @tyabus for the idea) 2025-01-14 11:55:28 +03:00
Alibek Omarov
785632a437 filesystem: minor refactoring in MD5_HashFile, use initializer instead of memset, limit buffer variable scope 2025-01-14 11:36:13 +03:00
Alibek Omarov
4be75af214 engine: client: always write buffer.dat so we will be able to request it from users. Append protocol number to it 2025-01-14 11:31:10 +03:00
Alibek Omarov
dcdca01e86 ref: gl: declare VBO globals static 2025-01-14 11:30:35 +03:00
Alibek Omarov
201d9c8f80 public: split VCS info to separate file for slightly better compilation speeds 2025-01-13 20:02:59 +03:00
Alibek Omarov
69341e36a3 public: expose commit and branch as global variables instead of functions 2025-01-13 20:02:59 +03:00
Alibek Omarov
3532a77643 public: move git commit and branch private to libpublic 2025-01-13 20:02:59 +03:00
Alibek Omarov
35dc072b26 filesystem: on Android, assume all paths are case-sensitive because doing Linux-specific ioctl check crashes FUSE driver and kills our app 2025-01-13 13:56:15 +03:00
Alibek Omarov
f92a3695b3 filesystem: fix fd leak in Platform_GetDirectoryCaseSensitivity 2025-01-13 13:54:42 +03:00
Alibek Omarov
a7b776f3b9 Revert "wscript: disable /showIncludes based msvcdeps for now, it causes issues with non-English locales"
Add option --enable-msvcdeps that we only enable on CI.

This reverts commit e58007aab3.
2025-01-10 21:48:43 +03:00
Alibek Omarov
f2fb668c9e android: now run exit() from Java side, as the next engine run is expected to be clean 2025-01-09 21:29:01 +03:00
Alibek Omarov
90e493fa3a engine: common: add Host_ExitInMain function that will force engine return from Host_Main() function, essentially making engine gracefully exit on Android
In future, this might be used everywhere, so users would be able to add custom
cleanup in some advanced game_launch implementation, on any platform.
2025-01-09 20:31:26 +03:00
Alibek Omarov
692bcc4073 engine: client: cl_tent: rewrite fizz effect, fix incorrect positoning along the way 2025-01-09 19:23:40 +03:00
Alibek Omarov
823642b10d readme: add link to ipa-reader.com for Xash word pronounciation [skip ci] 2025-01-09 17:13:56 +03:00
Alibek Omarov
07189ca2f5 engine: client: remove spammy message about not precached model on client side
Recently, mods (Brutal Half-Life v3 and MMod) started to call this function
every frame as a hacky way to detect presence of some model on the level. I
don't know why they do that, but users (who co-incidentally run engine with
-dev) has been complaining about console spam.

This is why we can't have things.
2025-01-09 17:10:54 +03:00
Alibek Omarov
0d55441830 engine: properly pass shutdown reason down to close log, helps to finally get rid of global finalmsg 2025-01-09 08:04:18 +03:00
Alibek Omarov
98ff33d9e2 engine: mod_bmodel: rewrite .lit file loader using normal stream io functions 2025-01-09 08:02:39 +03:00
Alibek Omarov
baa8fbaaec engine: mod_bmodel: fix misleading debug message telling about monochrome lighting when .lit file is used 2025-01-09 08:02:39 +03:00
Alibek Omarov
a0b46da04b engine: consolidate all box clipnodes used in studio models, pmove and server in a single place 2025-01-09 08:02:39 +03:00
Alibek Omarov
e70ba7ad64 engine: now, remove SUPPORT_BSP2_FORMAT macro, only prevent ref_soft from loading such maps 2025-01-09 08:02:39 +03:00
Alibek Omarov
8a41be9d47 engine: server: finally, make a union for large leaf indices in edict_t so we can run bsp2 maps without recompiling 2025-01-09 08:02:39 +03:00
Alibek Omarov
47aff9e30b engine: hack mnode_t struct so we can have 24-bit face and children indices to support BSP2 format in runtime 2025-01-09 08:02:39 +03:00
Alibek Omarov
ced8744ac9 engine: split edge struct into two with 16-bit indicies and 32-bit indicies to support BSP2 format in runtime 2025-01-08 14:46:07 +03:00
Alibek Omarov
734c07ddab engine: split clipnodes struct into two with 16-bit indicies and 32-bit indicies to support BSP2 format in runtime 2025-01-08 14:46:07 +03:00
Alibek Omarov
ceb603488a engine: platform: sdl: set up our custom log output function to catch all SDL errors in engine log file 2025-01-08 10:10:45 +03:00
Alibek Omarov
80bac157eb Update SDL to 2.30.11, use GitHub links for SDL, as it might be faster on Actions virtual machines 2025-01-08 09:42:53 +03:00
Alibek Omarov
b45581c630 Documentation: gameinfo: add a note about liblist.gam and gameinfo.txt changes 2025-01-08 01:22:23 +03:00
Alibek Omarov
d4fe3d6f8a filesystem: wscript: check d_type field in struct dirent, as this is an extension and some supported ports (like psvita) don't have it 2025-01-08 01:22:23 +03:00
Alibek Omarov
1907485895 filesystem: re-enable folder check for array returned by listdirectory with dirs_only set to true
According to glibc manual, not all filesystems support it and some might return DT_UNKNOWN.
2025-01-08 01:22:23 +03:00
Alibek Omarov
cfebb3e1d6 filesystem: massively rework how scanning game directories work
* No more conversion from liblist.gam to gameinfo.txt. We are using liblist.gam directly now.
  gameinfo.txt being native format to Xash3D not only remains, it takes priority over liblist.gam.
* Quake game directories now don't receive autogenerated gameinfo.txt.
* Empty directories don't get gameinfo.txt either, finally making it easier to support HD addon folders.
* If user still wishes to generate gameinfo.txt, there is now command fs_make_gameinfo that creates
  gameinfo.txt for currently running game.
* No more creating empty folders for RoDir. They are now created on demand.
2025-01-08 01:22:23 +03:00
Alibek Omarov
f9a0057c28 wscript: fix bsp2 build bruh 2025-01-08 01:22:23 +03:00
Alibek Omarov
ceb2b0c5e4 filesystem: add utility function FS_CheckForQuakePak that parses Quake PAK file and looks for specific files in it's root
Used to properly detect Quake game dirs.
2025-01-08 01:22:23 +03:00
Alibek Omarov
83b5edea88 engine: platform: sdl: clear key states on losing window focus 2025-01-06 16:38:16 +03:00
Alibek Omarov
9d2a85f11f engine: client: clear mouse state along with everything else in Key_ClearStates 2025-01-06 16:38:16 +03:00
Alibek Omarov
93d3df81ee engine: client: small refactoring and better explanation for char input handler for menus that don't query our extended API 2025-01-06 16:00:11 +03:00
Alibek Omarov
2a7f13151a engine: cmd: when command is already defined and has the same function pointer, show it as warning 2025-01-06 13:51:25 +03:00
Alibek Omarov
0c4f6ae61d engine: client: like GoldSrc, use cancelselect command for escape key. It is handled in client.dll and allows mods to override Esc key behavior 2025-01-06 13:51:25 +03:00
Andrey Akhmichin
b7150c3208 Documentation: opensource-mods.md: add "Earth Special Forces". 2025-01-02 05:35:33 +03:00
Alibek Omarov
d6ebff81e2 engine: client: vgui: track textures upload to avoid issues in buggy mods (that are probably will never get fixed) 2024-12-30 07:28:33 +03:00
Alibek Omarov
ce4148f351 engine: client: vgui: deprecate VGUI_CreateTexture and VGUI_UploadTextureBlock functions, as they never used by vgui_support module
* vgui_support used them in old engine for font cache, but we don't have that anymore.
2024-12-30 07:16:30 +03:00
Alibek Omarov
08c2632c14 engine: platform: nswitch: export _Unwind_GetIPInfo as required by cs16-client 2024-12-29 09:11:21 +03:00
Alibek Omarov
03eac904c1 public: make COM_FixSlashes inlined 2024-12-29 09:05:15 +03:00
Alibek Omarov
62f3354f4e mainui: update 2024-12-29 08:06:58 +03:00
Alibek Omarov
d5b6eff0c0 game_launch: fix --disable-menu-changegame by request 2024-12-29 08:06:50 +03:00
Alibek Omarov
c9ee844a24 scripts: gha: bundle SDL2 pdb file for better stacktraces on Windows 2024-12-29 02:08:05 +03:00
Alibek Omarov
687c9b004f engine: platform: sdl: set directsound as default audio driver on Windows, due to incompatibilities with mods 2024-12-29 02:01:23 +03:00
Alibek Omarov
14e79f04ac engine: prevent extra malloc in exec by checking if cfg already contains newline at EOL and appending it to cbuf if required 2024-12-28 16:03:49 +03:00
Alibek Omarov
5d6cf48687 engine: client: fix lightstyle timings on GoldSrc protocol, share the parser with Quake protocol handler 2024-12-28 01:40:59 +03:00
Alibek Omarov
88949a9ee6 engine: launcher: drop support for changing games from within internal launcher for platforms that do not have execv equivalent 2024-12-27 18:45:31 +03:00
Alibek Omarov
2d1e76199b engine: host: always print Host_Error text to log, even if console hasn't been enabled 2024-12-27 18:37:25 +03:00
Alibek Omarov
a5d78ecfd7 engine: be more verbose on why the engine is going to be shut down 2024-12-27 18:30:46 +03:00
Alibek Omarov
3576e474e6 engine: get rid of SendKeyEvents function, it only listened for WM_QUIT on Windows and we don't use that with SDL 2024-12-27 18:30:01 +03:00
Alibek Omarov
d50927f21f ref: gl: make r_ripple work with non-square non-pot textures, fix holes caused by unexpected transparency 2024-12-26 06:31:53 +03:00
Alibek Omarov
c6e3146290 ref: gl: fix broken water texture when it was discarded by R_UploadRipples 2024-12-26 03:49:12 +03:00
Alibek Omarov
9811988cce engine: server: set mapname globals field before making first call into DLL, as some mods tend to read it too early 2024-12-26 02:50:30 +03:00
103 changed files with 2220 additions and 1176 deletions

View File

@@ -66,7 +66,7 @@ jobs:
targetos: apple
targetarch: amd64
env:
SDL_VERSION: 2.30.9
SDL_VERSION: 2.30.11
GH_CPU_ARCH: ${{ matrix.targetarch }}
GH_CROSSCOMPILING: ${{ matrix.cross }}
steps:

View File

@@ -0,0 +1,5 @@
# Welcome to the GoldSource API docs
This documentation is an attempt on explaining inner workings of Valve's Half-Life (R) engine, also known as GoldSource, from programmer's perspective. A deep knowledge of Quake (NQ, QW and Q2) engines is required.
Everything discussed here is it's author assumption based on studying Xash3D source code and reverse engineering Half-Life mods. Xash3D API extensions is out of scope of this document and will be explained separately.

View File

@@ -0,0 +1,20 @@
# Initializing client.dll
`client.dll` has got multiple ways of initialization during it's life but we will go through the simplest and the most common route here.
Here we expect that reader has knowledge of loading dynamic shared objects on their platform. On Windows it's done through `LoadLibrary`/`GetProcAddress`/`FreeLibrary` functions, on POSIX-complaint systems it's done through `dlopen`/`dlsym`/`dlclose` functions.
## client.dll lifetime
`client.dll` is expected to be loaded during client initialization and unloaded on client shutdown. Essentially, in non-dedicated builds, it always exists during the engine lifetime. Judging by the API, it might look like `client.dll` might be safely unloaded and loaded again, for example for implementing classic `Change Game` functionality from WON versions of Half-Life, but in practice it's nearly impossible to do this clean in standard and portable manner. If you want to implement changing games, consider using `execv`-like functions.
## client.dll exported functions
The first thing you should do, is to acquire pointers to all exported functions, which you can find in the next chapter. Some of them are optional, and might not present in the `client.dll`, and will be labeled as such.
## client.dll initialization process
1. The first function you call is `Initialize` function, which lets `client.dll` to store a copy of an engine API functions.
2. Since SDK 2.0, `client.dll` have player movement code in it, which you must initialize through `HUD_PlayerMoveInit` function. `client.dll` might want to override player hulls, which can be grabbed with `HUD_GetHullBounds` function.
3. HUD functionality must be started up with `HUD_Init` function and can be de-initialized with `HUD_Shutdown` export function.
4. And finally, SDK 2.1 brings studio model renderer, which has separate set of API functions, which must be initialized with `HUD_GetStudioModelInterface` function.

View File

@@ -0,0 +1,286 @@
# List of client.dll exported functions
### `int Initialize( cl_enginefuncs_t *enginefuncs, int version )`
> [!IMPORTANT]
> This function is only called once.
Called after loading DLL and exports engine API to the `client.dll`.
* `enginefuncs` must be set to a pointer to a struct filled with engine function pointers, it will be described in next chapters.
* `version` must be always set to `7`. (HLSDK 1.0 uses version `6`, and is binary incompatible with `7`).
Return value: `0` on error, otherwise success.
### `void HUD_PlayerMoveInit( struct playermove_s *ppmove, int server )`
> [!IMPORTANT]
> This function is only called once.
Called on player movement prediction initialization, before HUD. In GoldSrc, engine only runs prediction loop, the players physics are implemented in `client.dll`.
* `ppmove` must be set to a pointer to a client instance of player movement structure, which also exports it's own API and will be discussed in the next chapters.
* `server` must be always set to `0` on client side.
This function is called only once.
### `void HUD_Init( void )`
> [!IMPORTANT]
> This function is only called once.
Called to initialize the HUD. At this moment engine should be ready to register new commands, console variables and user messages. No rendering or loading graphics done at this moment.
### `int HUD_GetStudioModelInterface( int version, struct r_studio_interface_s **ppinterface, struct engine_studio_api_s *pstudio )`
> [!IMPORTANT]
> This function is only called once.
This function is called after initializing HUD and exports studio model interface to the `client.dll`.
* `version` must be always set to `1`.
* `ppinterface` will be set by `client.dll` to a pointer to `r_studio_interface_s` structure.
* `pstudio` must be set to a pointer to `engine_studio_api_s` structure. The studio model interface will be discussed in the next chapters.
Return value: `0` on error, otherwise success.
### `void HUD_Shutdown( void )`
> [!IMPORTANT]
> This function is only called once.
Called at client shutdown.
### `int HUD_GetHullBounds( int hull, vec3_t mins, vec3_t maxs )`
> [!IMPORTANT]
> This function is only called once.
Called by the engine after `HUD_PlayerMoveInit` to let `client.dll` override hull bounds used for in player movement prediction.
* `hull` is the hull index (0: player standing, 1: player crouched, 2: point hull, 3: large hull).
* `mins` will contain hull mins.
* `maxs` will contain hull maxs.
Return value: if 0, don't override this hull and stop reading, non-zero means hull is valid _and_ there is more.
> [!NOTE]
> This function is broken in most implementations. It might return non-zero value, but don't write anything to the `mins` and `maxs` vectors, so be prepared to have some default values.
### `int HUD_VidInit( void )`
Called when client receives `svc_serverdata` message. It is called before any parsing of that message is done, thus the state is preserved from the previous connection. It lets `client.dll` to re-initialize graphics, if required. At this point, engine is expected to have video subsystem running, but no rendering is done here.
### `int HUD_Redraw( float flTime, int intermission )`
Called each frame to redraw the HUD. Only 2D is drawn here.
* `flTime` must be set to `cl.time`, i.e. synchronized with server.
* `intermission` must be set to `1` during intermission (set through `svc_intermission` message), otherwise it's set to `0`.
Return value: ignored.
### `void HUD_Reset( void )`
Called on demo recording or playback start and stop.
### `int HUD_UpdateClientData( client_data_t *cdata, float flTime )`
Called each frame after taking input.
* `cdata` contains pointer to `client_data_t` structure, populated by engine.
* `flTime` must be set to `cl.time`, i.e. synchronized with server.
Return value: if non-zero, will override engine viewangles and FOV value.
### `void HUD_PlayerMove( struct playermove_s *pmove, int server )`
When prediction is enabled, use this function to run player movement prediction. Note that this correlates to QW/Q2's prediction mechanism. It does not include weapon prediction.
* `pmove` is a pointer to playermove object
* `server` must be always set to `0`
### `char HUD_PlayerMoveTexture( char *name )`
Not used in the engine.
### `int HUD_ConnectionlessPacket( const netadr_t *from, const char *args, char *response_buffer, int *response_buffer_size )`
Called by the engine on unknown connectionless packets. Lets `client.dll` and `server.dll` have custom query protocol.
* `from` is set to network address where this packet is coming from.
* `args` raw network buffer, minus the connectionless packet header (0xFFFFFFFF).
* `response_buffer` set by `client.dll` if there is a response.
* `response_buffer_size` is initialized by the engine with buffer maximum size. Set by `client.dll` if there is a response.
Return value: non-zero if handled.
### `void HUD_Frame( double time )`
Called each frame after sending command to the remote server. No rendering is normally done in this function.
* `time` is the local delta time between previous and this frame (i.e. `host.frametime`)
### `void HUD_PostRunCmd( struct local_state_s *from, struct local_state_s *to, usercmd_t *cmd, int runfuncs, double time, unsigned int random_seed )`
Always called after `HUD_PlayerMove`, even if movement prediction is disabled. Used for weapon prediction stuff.
* `from` is a pointer to `local_state_s` object of the previous predicted frame
* `to` is a pointer to `local_state_s` object of this current frame
* `cmd` is current user command
* `runfuncs` is set to `1` if this frame was never predicted before, and to `0` if it is being predicted again
* `random_seed` is set to `incoming_acknowledged` plus number of predicted frames starting with `1`. This way it's synchronized between client and server.
### `int HUD_Key_Event( int down, int key, const char *current_binding )`
Called on keyboard event.
* `down` is set to `1` if key is being pressed or set to `0` on being released.
* `key` is set to the key number. The key IDs are predefined.
* `current_binding` is set to a null-terminated string with commands bound to this key
Return value: `0` if `client.dll` wants engine to ignore that key.
### `int HUD_AddEntity( int type, struct cl_entity_s *ent, const char *modelname )`
Called before adding entity to the rendering list.
* `type` is set to an entity type, see `entity_state_t::entityType`
* `ent` is a pointer to `cl_entity_t` object.
* `modelname` is a null-terminated string with that entity model name.
Return value: `0` if `client.dll` wants engine to not draw this entity.
### `void HUD_CreateEntities( void )`
Called each frame after all network entities (including players) are linked to let `client.dll` spawn client-side entities.
### `void HUD_StudioEvent( const struct mstudioevent_s *event, const struct cl_entity_s *ent )`
Studio models have events tied to the frame, on which engine calls this function.
* `event` is a pointer to a studio event object
* `ent` is a pointer to a client entity object
### `void HUD_TxferLocalOverrides( struct entity_state_s *state, const struct clientdata_s *client )`
When client processes entity updates, for local client it might be truncated, don't have enough precision, miss some critical info, so engine calls this function as `client.dll` might choose to use client data came from `svc_clientdata` message. Note that it is called on raw, non-interpolated networked entity state.
* `state` is a pointer to the local client entity state coming from the network
* `client` is a pointer to local client data object
### `void HUD_ProcessPlayerState( struct entity_state_s *dst, const struct entity_state_s *src )`
When client processes entity updates, it calls this function for player entities as `client.dll` might want to override some data or fill the missing parts, but usually it just copies from `src` to `dst`.
* `src` is a target pointer to the player entity data (stored in frames, for example)
* `dst` is a source pointer to the player entity data coming from network
### `void HUD_TxferPredictionData( struct entity_state_s *ps, const struct entity_state_s *pps, struct clientdata_s *pcd, const struct clientdata_s *ppcd, struct weapon_data_s *wd, const weapon_data_s *pwd )`
When client receives `svc_clientdata` message, this function is called before any parsing is done, so that the `client.dll` fills in data from prediction.
* `ps`, `pcd`, `wd` are pointers to current network frame data.
* `pps`, `ppcd`, `pwd` are pointers to predicted frame data.
### `void HUD_TempEntUpdate( double frametime, double client_time, double cl_gravity, TEMPENTITY **ppTempEntFree, TEMPENTITY **ppTempEntActive, int ( *AddVisibleEntity )( cl_entity_t *pEntity ), void ( *TempEntPlaySound)( TEMPENTITY *pTemp, float damp ))`
Called each frame after network entities are processed and after `HUD_CreateEntities`, to let `client.dll` process temporary entities logic.
* `frametime` is the delta between `cl.time` and `cl.oldtime`, i.e. server time.
* `client_time` is `cl.time`
* `gravity` is the synchronized gravity value from the server
* `ppTempEntFree` is a pointer to the head of linked list of free temp entities
* `ppTempEntActive` is a pointer to the head of linked list of active temp entities
* `AddVisibleEntity` is a pointer to function that lets `client.dll` add this entity to the rendering list.
* `TempEntPlaySound` is a pointer to function that's called by `client.dll` when temp entity needs to play predefined hit sound. The `damp` argument of this argument only makes sound to NOT play, if it's zero or negative.
### `void HUD_DrawNormalTriangles( void )`
Called each rendering frame to let `client.dll` draw custom solid triangles through TriAPI or direct OpenGL calls.
### `void HUD_DrawTransparentTriangles( void )`
Called each rendering frame to let `client.dll` draw custom transparent triangles through TriAPI or direct OpenGL calls.
### `struct cl_entity_s *HUD_GetUserEntity( int index )`
Called by engine when beam start/end indices are negative, thus allowing attaching beams to a temporary or client-only entity.
* `index` is the fixed up entity index, as beam start/end indices encode real entity index in low 12 bits (i.e. `beament_start & 0xFFF`).
### `void Demo_ReadBuffer( int size, unsigned char *buffer )`
Called by engine on demo playback, if `client.dll` saved some custom data on demo recording prior.
* `size` is the size of buffer in bytes
* `buffer` is the pointer to custom data stored by `client.dll` in demo
### `void CAM_Think( void )`
Called each frame before rendering starts to let `client.dll` run custom camera logic, like advanced thirdperson follow camera for example.
### `int CL_IsThirdPerson( void )`
Returns non-zero value if camera is in thirdperson mode, lets engine figure out whether add local client entity to the rendering list or not.
### `void CL_CameraOffset( vec3_t offset )`
Not used in the engine.
### `void CL_CreateMove( float frametime, usercmd_t *cmd, int active )`
Called when `usercmd_t` is being created to let `client.dll` record user commands before they are being sent over the network.
* `frametime` is the delta time between previous and current frames.
* `cmd` is the pointer to `usercmd_t` object, where player's intentions and impulses are added.
* `active` is set to `1` when client is finished signing on to the server (as movement commands are being sent even if client is not fully spawned yet).
### `void IN_ActivateMouse( void )`
Called from similarly named NQ/QW function.
### `void IN_DeactivateMouse( void )`
Called from similarly named NQ/QW function.
### `void IN_MouseEvent( int mstate )`
Called from similarly named NQ/QW function.
### `void IN_Accumulate( void )`
Called from similarly named NQ/QW function.
### `void IN_ClearStates( void )`
Called from similarly named NQ/QW function.
### `void V_CalcRefdef( struct ref_params_s *params )`
Called each frame before rendering starts. This is close to the similarly named function found in NQ/QW and lets `client.dll` to run custom view logic.
* `params` is the refdef parameters object. It is also used as return value and might request from the engine to not draw anything (by `onlyClientDraws` field) or to run this multiple times (by `nextview` field)
### `kbutton_t *KB_Find( const char *name )`
Called by engine to find extra keys and their state.
Only few are really used by the engine:
* `in_mlook` for mouse look
* `in_jlook` for joystick look
* `in_graph` for net_graph toggle
Return value: returns pointer to `kbutton_t` if found. `kbutton_t` structure matches the same structure that can be found in NQ and QW.
### `void HUD_DirectorMessage( int size, void *buf )`
> [!IMPORTANT]
> This function is optional.
Called to notify `client.dll` about an `svc_director` message. This feature is used for HLTV, though mods use it to spawn text messages or execute commands (bypassing `svc_stufftext` filter in old `client.dll`) but `svc_director` message structure isn't enforced by engine, so it in theory mods might modify it for their own needs.
* `size` is the size of the payload
* `buf` raw `svc_director` payload
### `void HUD_VoiceStatus( int entindex, qboolean talking )`
> [!IMPORTANT]
> This function is optional.
Called to notify `client.dll` about a client status of using voice chat.
* `entindex` an entity index (client index plus one, because zero is always world). When set to -1, notifies `client.dll` about local client recording. When set to -2, notifies `client.dll` about a loopback (i.e. local client's voice message was sent to server and received back)
* `talking` if true, this client is talking
### `void HUD_ChatInputPosition( int *x, int *y )`
> [!IMPORTANT]
> This function is optional.
When called, returns desired X and Y positions of chat box.

View File

@@ -0,0 +1,9 @@
# What is client.dll in GoldSource?
One of technical differences a programmer working with Quake engine might notice is the client.dll file. Historically, it appeared somewhere in between Alpha 0.52 and NetTest1 development and judging by Half-Life SDK 1.0 was meant to handle HUD rendering. The time moved forward, SDK 2.0 finalized `client.dll` API at version 7, added an in-game UI, custom input processing, player movement and weapon prediction, very basic rendering through TriAPI. SDK 2.1 added ability to re-define studio model rendering, and so on.
It somewhat resembles `cgame` module from Quake 3, but more crudely designed and sometimes feels like an afterthought, considering how many engine internal structures it exposes and the main point of incompatibilities with mods, which these days sometimes use `client.dll` as a way to inject custom rendering into the game.
In this document I will try to go through each step, letting you, dear reader, implement your own GoldSrc compatible API in your Quake fork, targetting vanilla Half-Life `client.dll` from latest update, which at the time of writing, is 25-th anniversary update.
Despite that we call it `client.dll`, since SDK 2.4 (unofficial naming, it's the first SDK Valve published on GitHub) it is considered portable and only has SDL2 and VGUI libraries in it's external dependences. It's only called this way to avoid possible misunderstandings with engine developers, who might interpret client as `cl_` prefixed part of Quake engine.

View File

@@ -0,0 +1,120 @@
# `client.dll` shared structs and enums
GoldSource exposes a lot of structs to `client.dll`. Some of them are specific to `client.dll` and only used for passing data between engine and client, some of them are used in the engine everywhere, but still exposed in the public SDK. However, many mods rely on internal structs as well, and I will try to shed light on them as well.
When implementing them in C, consider the default to 32-bit systems alignment of 4 bytes and ILP32 data type model.
This file won't have API structs, for that, there should be separate chapters.
## Enums
### Entity types enum
This enum has no real name.
| Value | Description |
|-------|-----------------|
| `0` | Normal entities |
| `1` | Players |
| `2` | Temp entities |
| `3` | Beams |
| `4` | Fragmented entities. |
### edict->solid
It matches QuakeWorld definition.
## `client_data_t`
Used only to pass data to `client.dll` through `HUD_UpdateClientData`.
| Type | Name | Description |
|----------|---------------|--------------------------------------------------------------|
| `vec3_t` | `origin` | Local client origin. Cannot be changed by `client.dll` here. |
| `vec3_t` | `viewangles` | Local client viewangles. This and next fields can be changed by `client.dll` |
| `int` | `iWeaponBits` | Bit vector of weapons held by client. |
| `float` | `fov` | Local client's field of view. |
## `netadr_t`
Matches Quake-2 structure with the similar name.
## `local_state_t`
Only consists of another structs. All of these structs are used to store prediction data, so one `local_state_t` for each predicted frame.
| Type | Name | Description |
|---------------------|---------------|--------------------------------------|
| `entity_state_t` | `playerstate` | Contains local player's entity state |
| `clientdata_t` | `client` | Additional information about local client. _Do not get confused with `client_data_t`, note the underscore._ |
| `weapon_data_t[64]` | `weapondata` | Array of 64 predictable weapons. |
## `entity_state_t`
This structure is similar to the one that can be found in QW, but contains much more data, though most of these fields are not used by the engine, but might be used by mods.
| Type | Name | Description |
|-------------|----------------|------------------------------------------------------|
| `int` | `entityType` | Entity type (see entity type enum above) |
| `int` | `number` | Index of this entity on the server |
| `float` | `msg_time` | Server time at which this entity had been updated |
| `int` | `messagenum` | `parsecount` at which this entity had been updated |
| `vec3_t` | `origin` | Non-interpolated entity position |
| `vec3_t` | `angles` | Non-interpolated entity angles |
| `int` | `modelindex` | Server model index |
| `int` | `sequence` | Model animation sequence |
| `int` | `frame` | Model animation frame |
| `int` | `colormap` | Texture's top and bottom colors, like Quake |
| `short` | `skin` | Texture number |
| `short` | `solid` | `edict->solid` enum |
| `int` | `effects` | Bitmask of entity effects |
| `float` | `scale` | Entity scale value |
| `byte` | `eflags` | |
| `int` | `eflags` | |
| `int` | `eflags` | |
| `color24` | `eflags` | |
| `int` | `eflags` | |
| `int` | `eflags` | |
| `float` | `eflags` | |
| `float` | `eflags` | |
| `int` | `eflags` | |
| `byte[4]` | `eflags` | |
| `byte[4]` | `eflags` | |
| `vec3_t` | `eflags` | |
| `vec3_t` | `eflags` | |
| `vec3_t` | `eflags` | |
| `vec3_t` | `maxs` | |
| `int` | `eflags` | |
| `int` | `eflags` | |
| `float` | `eflags` | |
| `float` | `eflags` | |
| `int` | `eflags` | |
| `int` | `eflags` | |
| `int` | `eflags` | |
| `qboolean` | `eflags` | |
| `int` | `eflags` | |
| `int` | `eflags` | |
| `vec3_t` | `eflags` | |
| `int` | `eflags` | |
| `int` | `eflags` | |
| `int` | `eflags` | |
| `int` | `eflags` | |
| `float` | `eflags` | |
| `float` | `eflags` | |
| `int` | `eflags` | |
| `vec3_t` | `eflags` | |
| `vec3_t` | `eflags` | |
| `float` | `eflags` | |
| `float` | `eflags` | |
| `int` | `iuser1` | Few more extra fields for mods |
| `int` | `iuser2` | |
| `int` | `iuser3` | |
| `int` | `iuser4` | |
| `float` | `fuser1` | |
| `float` | `fuser2` | |
| `float` | `fuser3` | |
| `float` | `fuser4` | |
| `vec3_t` | `vuser1` | |
| `vec3_t` | `vuser2` | |
| `vec3_t` | `vuser3` | |
| `vec3_t` | `vuser4` | |

View File

@@ -85,6 +85,9 @@ These strings are specific to Xash3D FWGS.
As Xash3D accidentally supports GoldSrc games, it also supports parsing liblist.gam.\
Xash3D will use this file if gameinfo.txt is absent, or if its modification timestamp is older than liblist.gam.
> [!NOTE]
> Starting from January 2025, Xash3D FWGS doesn't automatically generate gameinfo.txt from liblist.gam. The key conversion table still remains but if you wish to use gameinfo.txt instead of liblist.gam, you can execute `fs_make_gameinfo` in console.
For game creators who plan supporting only Xash3D, using this file is not recommended.
The table below defines conversion rules from liblist.gam to gameinfo.txt. Some keys' interpretation does differ from `gameinfo.txt`, in this case a note will be left. If `liblist.gam` key isn't present in this table, it's ignored.

View File

@@ -38,6 +38,9 @@ Uploaded to github by Oleg Cherkasky - https://github.com/gunrunners-paradise/Ct
## Deathmatch Classic
Available in Valve's Half-Life repository - https://github.com/ValveSoftware/halflife/tree/master/dmc
## Earth Special Forces
Alpha 2.0 - https://www.gamers-desire.de/details/2830
## ESHQ
Official github repository - https://github.com/adslbarxatov/xash3d-for-ESHQ

View File

@@ -2,7 +2,7 @@
[![builds.sr.ht status](https://builds.sr.ht/~a1batross/xash3d-fwgs.svg)](https://builds.sr.ht/~a1batross/xash3d-fwgs?) [![GitHub Actions Status](https://github.com/FWGS/xash3d-fwgs/actions/workflows/c-cpp.yml/badge.svg)](https://github.com/FWGS/xash3d-fwgs/actions/workflows/c-cpp.yml) [![FreeBSD Build Status](https://img.shields.io/cirrus/github/FWGS/xash3d-fwgs?label=freebsd%20build)](https://cirrus-ci.com/github/FWGS/xash3d-fwgs) [![Discord Server](https://img.shields.io/discord/355697768582610945.svg)](http://fwgsdiscord.mentality.rip/) \
[![Download Stable](https://img.shields.io/badge/download-stable-yellow)](https://github.com/FWGS/xash3d-fwgs/releases/latest) [![Download Testing](https://img.shields.io/badge/downloads-testing-orange)](https://github.com/FWGS/xash3d-fwgs/releases/tag/continuous)
Xash3D (pronounced `[ksɑʂ]`) FWGS is a game engine, aimed to provide compatibility with Half-Life Engine and extend it, as well as to give game developers well known workflow.
Xash3D ([pronounced](https://ipa-reader.com/?text=ks%C9%91%CA%82) `[ksɑʂ]`) FWGS is a game engine, aimed to provide compatibility with Half-Life Engine and extend it, as well as to give game developers well known workflow.
Xash3D FWGS is a heavily modified fork of an original [Xash3D Engine](https://www.moddb.com/engines/xash3d-engine) by Unkle Mike.

View File

@@ -30,6 +30,18 @@ public class XashActivity extends SDLActivity {
AndroidBug5497Workaround.assistActivity(this);
}
@Override
public void onDestroy()
{
super.onDestroy();
// Now that we don't exit from native code, we need to exit here, resetting
// application state (actually global variables that we don't cleanup on exit)
//
// When the issue with global variables will be resolved, remove that exit() call
System.exit(0);
}
@Override
protected String[] getLibraries() {
return new String[]{"SDL2", "xash"};

View File

@@ -16,8 +16,6 @@ GNU General Public License for more details.
#ifndef BSPFILE_H
#define BSPFILE_H
//#define SUPPORT_BSP2_FORMAT // allow to loading Darkplaces BSP2 maps (with broke binary compatibility)
/*
==============================================================================
@@ -65,7 +63,6 @@ BRUSH MODELS
#define MAX_MAP_CLIPNODES_BSP2 524288
// these limis not using by modelloader but only for displaying 'mapstats' correctly
#ifdef SUPPORT_BSP2_FORMAT
#define MAX_MAP_MODELS 2048 // embedded models
#define MAX_MAP_ENTSTRING 0x200000 // 2 Mb should be enough
#define MAX_MAP_PLANES 131072 // can be increased without problems
@@ -75,18 +72,6 @@ BRUSH MODELS
#define MAX_MAP_VERTS 524288 // can be increased without problems
#define MAX_MAP_FACES 262144 // can be increased without problems
#define MAX_MAP_MARKSURFACES 524288 // can be increased without problems
#else
// increased to match PrimeXT compilers
#define MAX_MAP_MODELS 1024 // embedded models
#define MAX_MAP_ENTSTRING 0x100000 // 1 Mb should be enough
#define MAX_MAP_PLANES 65536 // can be increased without problems
#define MAX_MAP_NODES 32767 // because negative shorts are leafs
#define MAX_MAP_CLIPNODES MAX_MAP_CLIPNODES_HLBSP // because negative shorts are contents
#define MAX_MAP_LEAFS 32767 // signed short limit
#define MAX_MAP_VERTS 65535 // unsigned short limit
#define MAX_MAP_FACES 65535 // unsigned short limit
#define MAX_MAP_MARKSURFACES 65535 // unsigned short limit
#endif
#define MAX_MAP_ENTITIES 8192 // network limit
#define MAX_MAP_TEXINFO MAX_MAP_FACES // in theory each face may have personal texinfo

View File

@@ -60,26 +60,29 @@ typedef struct
vec3_t position;
} mvertex_t;
typedef struct
typedef struct mclipnode32_s
{
int planenum;
#ifdef SUPPORT_BSP2_FORMAT
int children[2]; // negative numbers are contents
#else
short children[2]; // negative numbers are contents
#endif
} mclipnode_t;
int planenum;
int children[2]; // negative numbers are contents
} mclipnode32_t;
typedef struct mclipnode16_s
{
int planenum;
short children[2]; // negative numbers are contents
} mclipnode16_t;
// size is matched but representation is not
typedef struct
typedef struct medge32_s
{
#ifdef SUPPORT_BSP2_FORMAT
unsigned int v[2];
#else
} medge32_t;
typedef struct medge16_s
{
unsigned short v[2];
unsigned int cachededgeoffset;
#endif
} medge_t;
} medge16_t;
typedef struct texture_s
{
@@ -156,13 +159,31 @@ typedef struct mnode_s
// node specific
mplane_t *plane;
struct mnode_s *children[2];
#ifdef SUPPORT_BSP2_FORMAT
int firstsurface;
int numsurfaces;
#if !XASH_64BIT
union
{
struct mnode_s *children_[2];
struct
{
// the ordering is important
int child_0_leaf : 1;
int child_0_off : 23;
int firstsurface_1 : 8;
int child_1_leaf : 1;
int child_1_off : 23;
int numsurfaces_1 : 8;
};
};
unsigned short firstsurface_0;
unsigned short numsurfaces_0;
#else
unsigned short firstsurface;
unsigned short numsurfaces;
// in 64-bit ABI this struct has 4 more bytes of padding, let's use it!
struct mnode_s *children_[2];
unsigned short firstsurface_0;
unsigned short numsurfaces_0;
unsigned short firstsurface_1;
unsigned short numsurfaces_1;
#endif
} mnode_t;
@@ -203,7 +224,6 @@ typedef struct mleaf_s
int nummarksurfaces;
int cluster; // helper to acess to uncompressed visdata
byte ambient_sound_level[NUM_AMBIENTS];
} mleaf_t;
// surface extradata
@@ -291,7 +311,11 @@ struct msurface_s
typedef struct hull_s
{
mclipnode_t *clipnodes;
union
{
mclipnode16_t *clipnodes16;
mclipnode32_t *clipnodes32;
};
mplane_t *planes;
int firstclipnode;
int lastclipnode;
@@ -341,7 +365,12 @@ typedef struct model_s
mvertex_t *vertexes;
int numedges;
medge_t *edges;
union
{
medge16_t *edges16;
medge32_t *edges32;
};
int numnodes;
mnode_t *nodes;
@@ -356,7 +385,11 @@ typedef struct model_s
int *surfedges;
int numclipnodes;
mclipnode_t *clipnodes;
union
{
mclipnode16_t *clipnodes16;
mclipnode32_t *clipnodes32;
};
int nummarksurfaces;
msurface_t **marksurfaces;
@@ -550,16 +583,68 @@ typedef struct
#define ANIM_CYCLE 2
#define MOD_FRAMES 20
#define MAX_DEMOS 32
#define MAX_MOVIES 8
#define MAX_CDTRACKS 32
#define MAX_CLIENT_SPRITES 512 // SpriteTextures (0-256 hud, 256-512 client)
#define MAX_REQUESTS 64
STATIC_CHECK_SIZEOF( mnode_t, 52, 72 );
STATIC_CHECK_SIZEOF( mextrasurf_t, 324, 496 );
STATIC_CHECK_SIZEOF( decal_t, 60, 88 );
STATIC_CHECK_SIZEOF( mfaceinfo_t, 176, 304 );
// model flags (stored in model_t->flags)
#define MODEL_QBSP2 BIT( 28 ) // uses 32-bit types
// access functions
static inline mnode_t *node_child( const mnode_t *n, int side, const model_t *mod )
{
#if !XASH_64BIT
if( unlikely( mod->flags & MODEL_QBSP2 )) // MODEL_QBSP2
{
if( side == 0 )
{
if( n->child_0_leaf )
return (mnode_t *)(mod->leafs + n->child_0_off);
else
return (mnode_t *)(mod->nodes + n->child_0_off);
}
else
{
if( n->child_1_leaf )
return (mnode_t *)(mod->leafs + n->child_1_off);
else
return (mnode_t *)(mod->nodes + n->child_1_off);
}
}
return n->children_[side];
#else
return n->children_[side];
#endif
}
static inline void node_children( mnode_t *children[2], const mnode_t *n, const model_t *mod )
{
children[0] = node_child( n, 0, mod );
children[1] = node_child( n, 1, mod );
}
static inline int node_firstsurface( const mnode_t *n, const model_t *mod )
{
if( mod->flags & MODEL_QBSP2 )
return n->firstsurface_0 + ( n->firstsurface_1 << 16 );
else
return n->firstsurface_0;
}
static inline int node_numsurfaces( const mnode_t *n, const model_t *mod )
{
if( mod->flags & MODEL_QBSP2 )
return n->numsurfaces_0 + ( n->numsurfaces_1 << 16 );
else
return n->numsurfaces_0;
}
#endif//COM_MODEL_H

View File

@@ -153,6 +153,7 @@ static void CL_WriteErrorMessage( int current_count, sizebuf_t *msg )
FS_Write( fp, &cls.starting_count, sizeof( int ));
FS_Write( fp, &current_count, sizeof( int ));
FS_Write( fp, &cls.legacymode, sizeof( cls.legacymode ));
FS_Write( fp, MSG_GetData( msg ), MSG_GetMaxBytes( msg ));
FS_Close( fp );
@@ -168,7 +169,7 @@ list last 32 messages for debugging net troubleshooting
*/
void CL_WriteMessageHistory( void )
{
oldcmd_t *old, *failcommand;
oldcmd_t *old;
sizebuf_t *msg = &net_message;
int i, thecmd;
@@ -192,9 +193,8 @@ void CL_WriteMessageHistory( void )
thecmd++;
}
failcommand = &cls_message_debug.oldcmd[thecmd];
Con_Printf( "BAD: %3i:%s\n", MSG_GetNumBytesRead( msg ) - 1, CL_MsgInfo( failcommand->command ));
if( host_developer.value >= DEV_EXTENDED )
CL_WriteErrorMessage( MSG_GetNumBytesRead( msg ) - 1, msg );
old = &cls_message_debug.oldcmd[thecmd];
Con_Printf( S_RED "BAD: " S_DEFAULT "%i %04i %s\n", old->frame_number, old->starting_offset, CL_MsgInfo( old->command ));
CL_WriteErrorMessage( old->starting_offset, msg );
cls_message_debug.parsing = false;
}

View File

@@ -624,11 +624,10 @@ static void CL_ReadDemoUserCmd( qboolean discard )
if( !discard )
{
usercmd_t nullcmd;
const usercmd_t nullcmd = { 0 };
sizebuf_t buf;
demoangle_t *a;
memset( &nullcmd, 0, sizeof( nullcmd ));
MSG_Init( &buf, "UserCmd", data, sizeof( data ));
// a1ba: I have no proper explanation why

View File

@@ -127,8 +127,10 @@ static void R_SplitEntityOnNode( mnode_t *node )
}
// recurse down the contacted sides
if( sides & 1 ) R_SplitEntityOnNode( node->children[0] );
if( sides & 2 ) R_SplitEntityOnNode( node->children[1] );
if( sides & 1 )
R_SplitEntityOnNode( node_child( node, 0, cl.worldmodel ));
if( sides & 2 )
R_SplitEntityOnNode( node_child( node, 1, cl.worldmodel ));
}
/*

View File

@@ -2408,9 +2408,8 @@ CL_FindModelIndex
*/
static int GAME_EXPORT CL_FindModelIndex( const char *m )
{
char filepath[MAX_QPATH];
static float lasttimewarn;
int i;
char filepath[MAX_QPATH];
int i;
if( !COM_CheckString( m ))
return 0;
@@ -2427,13 +2426,6 @@ static int GAME_EXPORT CL_FindModelIndex( const char *m )
return i+1;
}
if( lasttimewarn < host.realtime )
{
// tell user about problem (but don't spam console)
Con_DPrintf( S_ERROR "Could not find index for model %s: not precached\n", filepath );
lasttimewarn = host.realtime + 1.0f;
}
return 0;
}

View File

@@ -1114,7 +1114,7 @@ static void GAME_EXPORT UI_ShellExecute( const char *path, const char *parms, in
Platform_ShellExecute( path, parms );
if( shouldExit )
Sys_Quit();
Sys_Quit( __func__ );
}
/*

View File

@@ -147,11 +147,6 @@ qboolean CL_IsRecordDemo( void )
return cls.demorecording;
}
qboolean CL_IsTimeDemo( void )
{
return cls.timedemo;
}
qboolean CL_DisableVisibility( void )
{
return cls.envshot_disable_vis;
@@ -598,7 +593,7 @@ CL_CreateCmd
*/
static void CL_CreateCmd( void )
{
usercmd_t nullcmd, *cmd;
usercmd_t nullcmd = { 0 }, *cmd;
runcmd_t *pcmd;
qboolean active;
double accurate_ms;
@@ -650,7 +645,6 @@ static void CL_CreateCmd( void )
}
else
{
memset( &nullcmd, 0, sizeof( nullcmd ));
cmd = &nullcmd;
}
@@ -825,7 +819,7 @@ static void CL_WritePacket( void )
buf.pData[key] = CRC32_BlockSequence( &buf.pData[key + 1], size, cls.netchan.outgoing_sequence );
COM_Munge( &buf.pData[key + 1], Q_min( size, 255 ), cls.netchan.outgoing_sequence );
}
else
else if( !Host_IsLocalClient( ))
{
int size = MSG_GetRealBytesWritten( &buf ) - key - 1;
buf.pData[key] = CRC32_BlockSequence( &buf.pData[key + 1], size, cls.netchan.outgoing_sequence );
@@ -965,7 +959,7 @@ CL_Quit_f
void CL_Quit_f( void )
{
CL_Disconnect();
Sys_Quit();
Sys_Quit( "command" );
}
/*
@@ -1761,8 +1755,8 @@ static size_t NONNULL CL_BuildMasterServerScanRequest( char *buf, size_t size, u
// let master know about client version
Info_SetValueForKey( info, "clver", XASH_VERSION, remaining );
Info_SetValueForKey( info, "nat", nat ? "1" : "0", remaining );
Info_SetValueForKey( info, "commit", Q_buildcommit(), remaining );
Info_SetValueForKey( info, "branch", Q_buildbranch(), remaining );
Info_SetValueForKey( info, "commit", g_buildcommit, remaining );
Info_SetValueForKey( info, "branch", g_buildbranch, remaining );
Info_SetValueForKey( info, "os", Q_buildos(), remaining );
Info_SetValueForKey( info, "arch", Q_buildarch(), remaining );

View File

@@ -1325,11 +1325,11 @@ void CL_ParseLightStyle( sizebuf_t *msg, connprotocol_t proto )
{
int style;
const char *s;
float f = 0.0f;
float f = cl.mtime[0];
style = MSG_ReadByte( msg );
s = MSG_ReadString( msg );
if( proto != PROTO_GOLDSRC )
if( proto != PROTO_GOLDSRC && proto != PROTO_QUAKE )
f = MSG_ReadFloat( msg );
CL_SetLightstyle( style, s, f );

View File

@@ -534,7 +534,7 @@ void CL_ParseGoldSrcServerMessage( sizebuf_t *msg )
{
if( MSG_CheckOverflow( msg ))
{
Host_Error( "CL_ParseServerMessage: overflow!\n" );
Host_Error( "%s: overflow!\n", __func__ );
return;
}

View File

@@ -970,9 +970,7 @@ void CL_ParseQuakeMessage( sizebuf_t *msg )
CL_ParseQuakeServerInfo( msg );
break;
case svc_lightstyle:
param1 = MSG_ReadByte( msg );
str = MSG_ReadString( msg );
CL_SetLightstyle( param1, str, cl.mtime[0] );
CL_ParseLightStyle( msg, PROTO_QUAKE );
break;
case svc_updatename:
param1 = MSG_ReadByte( msg );

View File

@@ -138,10 +138,7 @@ intptr_t CL_RenderGetParm( const int parm, const int arg, const qboolean checkRe
switch( parm )
{
case PARM_BSP2_SUPPORTED:
#ifdef SUPPORT_BSP2_FORMAT
return 1;
#endif
return 0;
case PARAM_GAMEPAUSED:
return cl.paused;
case PARM_CLIENT_INGAME:

View File

@@ -567,57 +567,54 @@ R_FizzEffect
Create a fizz effect
==============
*/
void GAME_EXPORT R_FizzEffect( cl_entity_t *pent, int modelIndex, int density )
void GAME_EXPORT R_FizzEffect( cl_entity_t *ent, int modelIndex, int density )
{
TEMPENTITY *pTemp;
int i, width, depth;
float angle, maxHeight, speed;
float xspeed, yspeed, zspeed;
vec3_t origin;
model_t *mod;
const float base_time = cl.time - 0.1f;
model_t *mod = CL_ModelHandle( modelIndex );
vec3_t volume, mins, maxs;
vec2_t speed;
int i;
if( !pent || !pent->model || !modelIndex )
if( !ent || !ent->model || !modelIndex || !mod )
return;
if(( mod = CL_ModelHandle( modelIndex )) == NULL )
return;
VectorCopy( ent->model->mins, mins );
VectorCopy( ent->model->maxs, maxs );
maxHeight = pent->model->maxs[2] - pent->model->mins[2];
width = pent->model->maxs[0] - pent->model->mins[0];
depth = pent->model->maxs[1] - pent->model->mins[1];
if( ent->angles[1] != 0.0f )
{
const float base_speed = ( ent->curstate.rendercolor.b ? -1.0f : 1.0f ) * ( ent->curstate.rendercolor.r * 256.0f + ent->curstate.rendercolor.g );
SinCos( DEG2RAD( ent->angles[1] ), &speed[1], &speed[0] );
speed[0] *= base_speed;
speed[1] *= base_speed;
}
else speed[0] = speed[1] = 0.0f;
speed = ( pent->curstate.rendercolor.r<<8 | pent->curstate.rendercolor.g );
if( pent->curstate.rendercolor.b )
speed = -speed;
angle = DEG2RAD( pent->angles[YAW] );
SinCos( angle, &yspeed, &xspeed );
xspeed *= speed;
yspeed *= speed;
VectorSubtract( maxs, mins, volume );
for( i = 0; i <= density; i++ )
{
origin[0] = mod->mins[0] + COM_RandomLong( 0, width - 1 );
origin[1] = mod->mins[1] + COM_RandomLong( 0, depth - 1 );
origin[2] = mod->mins[2];
pTemp = CL_TempEntAlloc( origin, mod );
TEMPENTITY *tent;
vec3_t origin;
if ( !pTemp ) return;
VectorCopy( mins, origin );
origin[0] += COM_RandomLong( 0, (int)volume[0] - 1 );
origin[1] += COM_RandomLong( 0, (int)volume[1] - 1 );
pTemp->flags |= FTENT_SINEWAVE;
if( !( tent = CL_TempEntAlloc( origin, mod )))
return;
pTemp->x = origin[0];
pTemp->y = origin[1];
tent->x = origin[0];
tent->y = origin[1];
tent->die = base_time;
tent->flags |= FTENT_SINEWAVE;
tent->entity.curstate.rendermode = kRenderTransAlpha;
Vector2Copy( speed, tent->entity.baseline.origin );
zspeed = COM_RandomLong( 80, 140 );
VectorSet( pTemp->entity.baseline.origin, xspeed, yspeed, zspeed );
pTemp->die = cl.time + ( maxHeight / zspeed ) - 0.1f;
pTemp->entity.curstate.frame = COM_RandomLong( 0, pTemp->frameMax );
// Set sprite scale
pTemp->entity.curstate.scale = 1.0f / COM_RandomFloat( 2.0f, 5.0f );
pTemp->entity.curstate.rendermode = kRenderTransAlpha;
pTemp->entity.curstate.renderamt = 255;
tent->entity.baseline.origin[2] = COM_RandomLong( 80, 140 );
tent->die += volume[2] / tent->entity.baseline.origin[2];
tent->entity.curstate.frame = COM_RandomLong( 0, tent->frameMax );
tent->entity.curstate.scale = 1.0f / COM_RandomFloat( 2.0f, 5.0f );
}
}

View File

@@ -466,8 +466,8 @@ static void R_ShowTree_r( mnode_t *node, float x, float y, float scale, int show
R_DrawNodeConnection( x, y, x + scale, y + scale );
}
R_ShowTree_r( node->children[1], x - scale, y + scale, downScale, shownodes, viewleaf );
R_ShowTree_r( node->children[0], x + scale, y + scale, downScale, shownodes, viewleaf );
R_ShowTree_r( node_child( node, 1, cl.worldmodel ), x - scale, y + scale, downScale, shownodes, viewleaf );
R_ShowTree_r( node_child( node, 0, cl.worldmodel ), x + scale, y + scale, downScale, shownodes, viewleaf );
world.recursion_level--;
}
@@ -482,7 +482,7 @@ static void R_ShowTree( void )
return;
world.recursion_level = 0;
viewleaf = Mod_PointInLeaf( refState.vieworg, cl.worldmodel->nodes );
viewleaf = Mod_PointInLeaf( refState.vieworg, cl.worldmodel->nodes, cl.worldmodel );
ref.dllFuncs.TriRenderMode( kRenderTransTexture );

View File

@@ -266,7 +266,7 @@ static qboolean Touch_DumpConfig( const char *name, const char *profilename )
}
FS_Printf( f, "//=======================================================================\n");
FS_Printf( f, "//\tGenerated by "XASH_ENGINE_NAME" (%i, %s, %s, %s-%s)\n", Q_buildnum(), Q_buildcommit(), Q_buildbranch(), Q_buildos(), Q_buildarch());
FS_Printf( f, "//\tGenerated by "XASH_ENGINE_NAME" (%i, %s, %s, %s-%s)\n", Q_buildnum(), g_buildcommit, g_buildbranch, Q_buildos(), Q_buildarch());
FS_Printf( f, "//\t\t\ttouchscreen config\n" );
FS_Printf( f, "//=======================================================================\n" );
FS_Printf( f, "\ntouch_config_file \"%s\"\n", profilename );
@@ -1101,7 +1101,7 @@ void Touch_Init( void )
Touch_AddDefaultButton( "duck", "touch_default/crouch", "+duck", 0.880000, 0.757428, 1.000000, 0.984656, color, 2, 1, 0 );
Touch_AddDefaultButton( "tduck", "touch_default/tduck", ";+duck", 0.560000, 0.833171, 0.620000, 0.946785, color, 2, 1, 0 );
Touch_AddDefaultButton( "edit", "touch_default/settings", "touch_enableedit", 0.420000, 0.000000, 0.500000, 0.151486, color, 2, 1, 32 );
Touch_AddDefaultButton( "menu", "touch_default/menu", "escape", 0.000000, 0.833171, 0.080000, 0.984656, color, 2, 1, 0 );
Touch_AddDefaultButton( "menu", "touch_default/menu", "cancelselect", 0.000000, 0.833171, 0.080000, 0.984656, color, 2, 1, 0 );
Touch_AddDefaultButton( "spray", "touch_default/spray", "impulse 201", 0.680000, 0.000000, 0.760000, 0.142222, color, 2, 1, 8 );
Touch_AddDefaultButton( "voicechat", "touch_default/microphone", "+voicerecord", 0.780000, 0.817778, 0.860000, 0.960000, color, 2, 1, 8 );

View File

@@ -633,8 +633,6 @@ Called every frame, even if not generating commands
*/
void Host_InputFrame( void )
{
Platform_SendKeyEvents();
IN_Commands();
IN_MouseMove();

View File

@@ -40,7 +40,7 @@ static const keyname_t keynames[] =
{
{"TAB", K_TAB, "" },
{"ENTER", K_ENTER, "" },
{"ESCAPE", K_ESCAPE, "escape" }, // hardcoded
{"ESCAPE", K_ESCAPE, "cancelselect" }, // hardcoded
{"SPACE", K_SPACE, "+jump" },
{"BACKSPACE", K_BACKSPACE, "" },
{"UPARROW", K_UPARROW, "+forward" },
@@ -106,7 +106,7 @@ static const keyname_t keynames[] =
{"Y_BUTTON", K_Y_BUTTON, "impulse 100"}, // Flashlight
{"BACK", K_BACK_BUTTON, "pause"}, // Menu
{"MODE", K_MODE_BUTTON, ""},
{"START", K_START_BUTTON, "escape"},
{"START", K_START_BUTTON, "cancelselect"},
{"STICK1", K_LSTICK, "+speed"},
{"STICK2", K_RSTICK, "+duck"},
{"L1_BUTTON", K_L1_BUTTON, "+duck"},
@@ -339,6 +339,12 @@ static void Key_Unbind_f( void )
return;
}
if( b == K_ESCAPE )
{
Con_Printf( "Can't unbind ESCAPE key\n" );
return;
}
Key_SetBinding( b, "" );
}
@@ -358,8 +364,8 @@ static void Key_Unbindall_f( void )
}
// set some defaults
Key_SetBinding( K_ESCAPE, "escape" );
Key_SetBinding( K_START_BUTTON, "escape" );
Key_SetBinding( K_ESCAPE, "cancelselect" );
Key_SetBinding( K_START_BUTTON, "cancelselect" );
}
/*
@@ -737,18 +743,24 @@ void GAME_EXPORT Key_Event( int key, int down )
if( cls.key_dest == key_menu )
{
// only non printable keys passed
// classic Xash3D menus don't have an extension that tells engine
// to enable text input
if( !gameui.use_extended_api )
Key_EnableTextInput( true, false );
//pass printable chars for old menus
if( !gameui.use_extended_api && !host.textmode && down && ( key >= 32 ) && ( key <= 'z' ) )
{
if( Key_IsDown( K_SHIFT ) )
// we don't know if menu wants text input or not
// enable it unconditionally
Key_EnableTextInput( true, false );
// pass this key to the menu, if printable
if( !host.textmode && down && ( key >= 32 ) && ( key <= 'z' ))
{
key += 'A' - 'a';
if( Key_IsDown( K_SHIFT ))
key += 'A' - 'a';
UI_CharEvent( key );
}
UI_CharEvent( key );
}
UI_KeyEvent( key, down );
return;
}
@@ -844,11 +856,12 @@ void GAME_EXPORT Key_ClearStates( void )
int i;
// don't clear keys during changelevel
if( cls.changelevel ) return;
if( cls.changelevel )
return;
for( i = 0; i < 256; i++ )
{
if( keys[i].down )
if( keys[i].down && i < K_MOUSE1 && i > K_MOUSE5 )
Key_Event( i, false );
keys[i].down = 0;
@@ -856,6 +869,9 @@ void GAME_EXPORT Key_ClearStates( void )
keys[i].gamedown = 0;
}
for( i = K_MOUSE1; i < K_MOUSE5; i++ ) // from K_MOUSE1 to K_MOUSE5
IN_MouseEvent( i - K_MOUSE1, false );
if( clgame.hInstance )
clgame.dllFuncs.IN_ClearStates();
}

View File

@@ -470,16 +470,16 @@ out_free:
* This is a stack of the clipnodes we have traversed
* "sides" indicates which side we went down each time
*/
static mclipnode_t *node_stack[MAX_CLIPNODE_DEPTH];
static int node_stack[MAX_CLIPNODE_DEPTH];
static int side_stack[MAX_CLIPNODE_DEPTH];
static uint node_stack_depth;
static void push_node( mclipnode_t *node, int side )
static void push_node( int nodenum, int side )
{
if( node_stack_depth == MAX_CLIPNODE_DEPTH )
Host_Error( "node stack overflow\n" );
node_stack[node_stack_depth] = node;
node_stack[node_stack_depth] = nodenum;
side_stack[node_stack_depth] = side;
node_stack_depth++;
}
@@ -502,22 +502,27 @@ static void free_hull_polys( hullnode_t *hull_polys )
}
}
static void hull_windings_r( hull_t *hull, mclipnode_t *node, hullnode_t *polys, hull_model_t *model );
static void hull_windings_r( hull_t *hull, int nodenum, hullnode_t *polys, hull_model_t *model );
static void do_hull_recursion( hull_t *hull, mclipnode_t *node, int side, hullnode_t *polys, hull_model_t *model )
static void do_hull_recursion( hull_t *hull, int nodenum, int side, hullnode_t *polys, hull_model_t *model )
{
winding_t *w, *next;
int childnum;
if( node->children[side] >= 0 )
if( world.version == QBSP2_VERSION )
childnum = hull->clipnodes32[nodenum].children[side];
else
childnum = hull->clipnodes16[nodenum].children[side];
if( childnum >= 0 )
{
mclipnode_t *child = hull->clipnodes + node->children[side];
push_node( node, side );
hull_windings_r( hull, child, polys, model );
push_node( nodenum, side );
hull_windings_r( hull, childnum, polys, model );
pop_node();
}
else
{
switch( node->children[side] )
switch( childnum )
{
case CONTENTS_EMPTY:
case CONTENTS_WATER:
@@ -542,20 +547,25 @@ static void do_hull_recursion( hull_t *hull, mclipnode_t *node, int side, hullno
}
break;
default:
Host_Error( "bad contents: %i\n", node->children[side] );
Host_Error( "bad contents: %i\n", childnum );
break;
}
}
}
static void hull_windings_r( hull_t *hull, mclipnode_t *node, hullnode_t *polys, hull_model_t *model )
static void hull_windings_r( hull_t *hull, int nodenum, hullnode_t *polys, hull_model_t *model )
{
mplane_t *plane = hull->planes + node->planenum;
mplane_t *plane;
hullnode_t frontlist = LIST_HEAD_INIT( frontlist );
hullnode_t backlist = LIST_HEAD_INIT( backlist );
winding_t *w, *next, *front, *back;
int i;
if( world.version == QBSP2_VERSION )
plane = hull->planes + hull->clipnodes32[nodenum].planenum;
else
plane = hull->planes + hull->clipnodes16[nodenum].planenum;
list_for_each_entry_safe( w, next, polys, chain )
{
// PARANIOA - PAIR CHECK
@@ -601,7 +611,13 @@ static void hull_windings_r( hull_t *hull, mclipnode_t *node, hullnode_t *polys,
for( i = 0; w && i < node_stack_depth; i++ )
{
mplane_t *p = hull->planes + node_stack[i]->planenum;
mplane_t *p;
if( world.version == QBSP2_VERSION )
p = hull->planes + hull->clipnodes32[node_stack[i]].planenum;
else
p = hull->planes + hull->clipnodes16[node_stack[i]].planenum;
w = winding_clip( w, p, false, side_stack[i], 0.00001 );
}
@@ -625,8 +641,8 @@ static void hull_windings_r( hull_t *hull, mclipnode_t *node, hullnode_t *polys,
Con_Printf( S_WARN "new winding was clipped away!\n" );
}
do_hull_recursion( hull, node, 0, &frontlist, model );
do_hull_recursion( hull, node, 1, &backlist, model );
do_hull_recursion( hull, nodenum, 0, &frontlist, model );
do_hull_recursion( hull, nodenum, 1, &backlist, model );
}
static void remove_paired_polys( hull_model_t *model )
@@ -655,7 +671,7 @@ static void make_hull_windings( hull_t *hull, hull_model_t *model )
if( hull->planes != NULL )
{
hull_windings_r( hull, hull->clipnodes + hull->firstclipnode, &head, model );
hull_windings_r( hull, hull->firstclipnode, &head, model );
remove_paired_polys( model );
}
Con_Reportf( "%i hull polys\n", model->num_polys );

View File

@@ -300,6 +300,12 @@ static qboolean R_Init_Video_( const int type )
return R_Init_Video( type );
}
static mleaf_t *pfnMod_PointInLeaf( const vec3_t p, mnode_t *node )
{
// FIXME: get rid of this on next RefAPI update
return Mod_PointInLeaf( p, node, cl.models[1] );
}
static const ref_api_t gEngfuncs =
{
pfnEngineGetParm,
@@ -340,7 +346,7 @@ static const ref_api_t gEngfuncs =
Mod_SampleSizeForFace,
Mod_BoxVisible,
Mod_PointInLeaf,
pfnMod_PointInLeaf,
R_DrawWorldHull,
R_DrawModelHull,

View File

@@ -996,7 +996,7 @@ static void S_UpdateAmbientSounds( void )
// calc ambient sound levels
if( !cl.worldmodel ) return;
leaf = Mod_PointInLeaf( s_listener.origin, cl.worldmodel->nodes );
leaf = Mod_PointInLeaf( s_listener.origin, cl.worldmodel->nodes, cl.worldmodel );
if( !leaf || !s_ambient_level.value )
{

View File

@@ -25,20 +25,29 @@ GNU General Public License for more details.
#define VGUI_MAX_TEXTURES 1024
typedef struct vgui_reusable_texture_s
{
int gl_texturenum;
byte hash[16];
} vgui_reusable_texture_t;
typedef struct vgui_static_s
{
qboolean initialized;
VGUI_DefaultCursor cursor;
vguiapi_t dllFuncs;
int textures[VGUI_MAX_TEXTURES];
vgui_reusable_texture_t *textures;
int texture_id;
int max_textures;
int bound_texture;
byte color[4];
qboolean enable_texture;
HINSTANCE hInstance;
poolhandle_t mempool;
enum VGUI_KeyCode virtualKeyTrans[256];
} vgui_static_t;
@@ -49,9 +58,16 @@ static CVAR_DEFINE_AUTO( vgui_utf8, "0", FCVAR_ARCHIVE, "enable utf-8 support fo
static void GAME_EXPORT VGUI_DrawInit( void )
{
memset( vgui.textures, 0, sizeof( vgui.textures ));
if( vgui.mempool )
Mem_EmptyPool( vgui.mempool );
else vgui.mempool = Mem_AllocPool( "VGui Support Pool" );
vgui.textures = NULL;
memset( vgui.color, 0, sizeof( vgui.color ));
vgui.texture_id = vgui.bound_texture = 0;
vgui.texture_id = 0;
vgui.bound_texture = 0;
vgui.max_textures = 0;
vgui.enable_texture = true;
}
@@ -60,28 +76,79 @@ static void GAME_EXPORT VGUI_DrawShutdown( void )
int i;
for( i = 1; i < vgui.texture_id; i++ )
ref.dllFuncs.GL_FreeTexture( vgui.textures[i] );
ref.dllFuncs.GL_FreeTexture( vgui.textures[i].gl_texturenum );
Mem_FreePool( &vgui.mempool );
vgui.textures = NULL;
memset( vgui.color, 0, sizeof( vgui.color ));
vgui.texture_id = 0;
vgui.bound_texture = 0;
vgui.max_textures = 0;
}
static int GAME_EXPORT VGUI_GenerateTexture( void )
{
if( ++vgui.texture_id >= VGUI_MAX_TEXTURES )
Host_Error( "%s: VGUI_MAX_TEXTURES limit exceeded\n", __func__ );
// allocate new
if( vgui.texture_id + 1 >= vgui.max_textures )
{
if( vgui.max_textures + VGUI_MAX_TEXTURES >= VGUI_MAX_TEXTURES * VGUI_MAX_TEXTURES )
{
// in theory it might look up texture that hasn't been bound for a while and
// reuse that but it will eventually overwrite some important textures anyway
Con_Printf( S_ERROR "%s: Refusing resizing VGUI textures array due to memory leak\n", __func__ );
return vgui.texture_id;
}
return vgui.texture_id;
vgui.max_textures += VGUI_MAX_TEXTURES;
// this potentially might leak memory if VGUI is used incorrectly!
// (like in Cry of Fear)
vgui.textures = Mem_Realloc( vgui.mempool, vgui.textures, sizeof( *vgui.textures ) * vgui.max_textures );
// warn mod developer
if( vgui.max_textures >= VGUI_MAX_TEXTURES * 4 )
Con_Printf( S_ERROR "%s: Potential memory leak in VGUI code is detected!\n", __func__ );
}
return ++vgui.texture_id;
}
static void GAME_EXPORT VGUI_UploadTexture( int id, const char *buffer, int width, int height )
{
rgbdata_t r_image = { 0 };
char texName[32];
MD5Context_t ctx;
byte hash[16];
if( id <= 0 || id >= VGUI_MAX_TEXTURES )
if( id <= 0 || id >= vgui.max_textures || width <= 0 || height <= 0 )
{
Con_DPrintf( S_ERROR "%s: bad texture %i. Ignored\n", __func__, id );
return;
}
// need to do this as some mods tend to upload same texture over and over
// exhausing engine-wide limit on textures and leaking vram
MD5Init( &ctx );
MD5Update( &ctx, buffer, width * height * 4 );
MD5Final( hash, &ctx );
// it's a new texture, try to find a copy
if( vgui.textures[id].gl_texturenum == 0 )
{
int i;
for( i = 1; i < vgui.texture_id; i++ )
{
if( vgui.textures[i].gl_texturenum != 0 && !memcmp( vgui.textures[i].hash, hash, sizeof( hash )))
{
// copy data to new texture id
vgui.textures[id] = vgui.textures[i];
return;
}
}
}
Q_snprintf( texName, sizeof( texName ), "*vgui%i", id );
r_image.width = width;
@@ -91,51 +158,29 @@ static void GAME_EXPORT VGUI_UploadTexture( int id, const char *buffer, int widt
r_image.flags = IMAGE_HAS_COLOR|IMAGE_HAS_ALPHA;
r_image.buffer = (byte*)buffer;
vgui.textures[id] = GL_LoadTextureInternal( texName, &r_image, TF_IMAGE );
vgui.textures[id].gl_texturenum = GL_LoadTextureInternal( texName, &r_image, TF_IMAGE );
memcpy( vgui.textures[id].hash, hash, sizeof( hash ));
}
static void GAME_EXPORT VGUI_CreateTexture( int id, int width, int height )
{
rgbdata_t r_image = { 0 };
char texName[32];
// nothing uses it, it can be removed
Host_Error( "%s: deprecated\n", __func__ );
if( id <= 0 || id >= VGUI_MAX_TEXTURES )
{
Con_DPrintf( S_ERROR "%s: bad texture %i. Ignored\n", __func__, id );
return;
}
Q_snprintf( texName, sizeof( texName ), "*vgui%i", id );
r_image.width = width;
r_image.height = height;
r_image.type = PF_RGBA_32;
r_image.size = width * height * 4;
r_image.flags = IMAGE_HAS_COLOR|IMAGE_HAS_ALPHA;
r_image.buffer = NULL;
vgui.textures[id] = GL_LoadTextureInternal( texName, &r_image, TF_IMAGE );
vgui.bound_texture = id;
}
static void GAME_EXPORT VGUI_UploadTextureBlock( int id, int drawX, int drawY, const byte *rgba, int blockWidth, int blockHeight )
{
if( id <= 0 || id >= VGUI_MAX_TEXTURES || vgui.textures[id] == 0 )
{
Con_DPrintf( S_ERROR "%s: bad texture %i. Ignored\n", __func__, id );
return;
}
ref.dllFuncs.VGUI_UploadTextureBlock( drawX, drawY, rgba, blockWidth, blockHeight );
vgui.bound_texture = id;
// nothing uses it, it can be removed
Host_Error( "%s: deprecated\n", __func__ );
}
static void GAME_EXPORT VGUI_BindTexture( int id )
{
if( id <= 0 || id >= VGUI_MAX_TEXTURES || !vgui.textures[id] )
if( id <= 0 || id >= vgui.max_textures || !vgui.textures[id].gl_texturenum )
id = 1; // NOTE: same as bogus index 2700 in GoldSrc
ref.dllFuncs.GL_Bind( XASH_TEXTURE0, vgui.textures[id] );
ref.dllFuncs.GL_Bind( XASH_TEXTURE0, vgui.textures[id].gl_texturenum );
vgui.bound_texture = id;
}
@@ -144,7 +189,7 @@ static void GAME_EXPORT VGUI_GetTextureSizes( int *w, int *h )
int texnum;
if( vgui.bound_texture )
texnum = vgui.textures[vgui.bound_texture];
texnum = vgui.textures[vgui.bound_texture].gl_texturenum;
else
texnum = R_GetBuiltinTexture( REF_DEFAULT_TEXTURE );
@@ -187,7 +232,7 @@ static void GAME_EXPORT VGUI_DrawQuad( const vpoint_t *ul, const vpoint_t *lr )
t2 = lr->coord[1];
ref.dllFuncs.Color4ub( vgui.color[0], vgui.color[1], vgui.color[2], vgui.color[3] );
ref.dllFuncs.R_DrawStretchPic( x, y, w, h, s1, t1, s2, t2, vgui.textures[vgui.bound_texture] );
ref.dllFuncs.R_DrawStretchPic( x, y, w, h, s1, t1, s2, t2, vgui.textures[vgui.bound_texture].gl_texturenum );
}
else
{

View File

@@ -142,28 +142,33 @@ void Cbuf_AddFilteredText( const char *text )
Cbuf_InsertText
Adds command text immediately after the current command
Adds a \n to the text
============
*/
static void Cbuf_InsertTextToBuffer( cmdbuf_t *buf, const char *text )
static void Cbuf_InsertTextToBuffer( cmdbuf_t *buf, const char *text, size_t len, size_t requested_len )
{
int l = Q_strlen( text );
if(( buf->cursize + l ) >= buf->maxsize )
if(( buf->cursize + requested_len ) >= buf->maxsize )
{
Con_Reportf( S_WARN "%s: overflow\n", __func__ );
}
else
{
memmove( buf->data + l, buf->data, buf->cursize );
memcpy( buf->data, text, l );
buf->cursize += l;
memmove( buf->data + len, buf->data, buf->cursize );
memcpy( buf->data, text, len );
buf->cursize += len;
}
}
void Cbuf_InsertTextLen( const char *text, size_t len, size_t requested_len )
{
// sometimes we need to insert more data than we have
// but also prevent overflow
Cbuf_InsertTextToBuffer( &cmd_text, text, len, requested_len );
}
void Cbuf_InsertText( const char *text )
{
Cbuf_InsertTextToBuffer( &cmd_text, text );
size_t l = Q_strlen( text );
Cbuf_InsertTextToBuffer( &cmd_text, text, l, l );
}
/*
@@ -712,7 +717,7 @@ int Cmd_AddCommandEx( const char *cmd_name, xcommand_t function, const char *cmd
}
else
{
Con_DPrintf( S_ERROR "%s: %s already defined\n", funcname, cmd_name );
Con_DPrintf( "%s%s: %s already defined\n", cmd->function == function ? S_WARN : S_ERROR, funcname, cmd_name );
return 0;
}
}
@@ -1010,9 +1015,10 @@ static void Cmd_ExecuteStringWithPrivilegeCheck( const char *text, qboolean isPr
if( a )
{
size_t len = Q_strlen( a->value );
Cbuf_InsertTextToBuffer(
isPrivileged ? &cmd_text : &filteredcmd_text,
a->value );
a->value, len, len );
return;
}
}

View File

@@ -72,8 +72,6 @@ GNU General Public License for more details.
#define CVAR_GLCONFIG_DESCRIPTION "enable or disable %s"
#define DEFAULT_BSP_BUILD_ERROR "%s can't be loaded in this build. Please rebuild engine with enabled SUPPORT_BSP2_FORMAT\n"
#define DEFAULT_UPDATE_PAGE "https://github.com/FWGS/xash3d-fwgs/releases/latest"
#define XASH_ENGINE_NAME "Xash3D FWGS"

View File

@@ -301,7 +301,6 @@ typedef struct host_parm_s
poolhandle_t mempool; // static mempool for misc allocations
poolhandle_t imagepool; // imagelib mempool
poolhandle_t soundpool; // soundlib mempool
string finalmsg; // server shutdown final message
string downloadfile; // filename to be downloading
int downloadcount; // how many files remain to downloading
char deferred_cmd[128];// deferred commands
@@ -425,6 +424,7 @@ void Cbuf_AddText( const char *text );
void Cbuf_AddTextf( const char *text, ... ) FORMAT_CHECK( 1 );
void Cbuf_AddFilteredText( const char *text );
void Cbuf_InsertText( const char *text );
void Cbuf_InsertTextLen( const char *text, size_t len, size_t requested_len );
void Cbuf_ExecStuffCmds( void );
void Cbuf_Execute (void);
qboolean Cmd_CurrentCommandIsPrivileged( void );
@@ -547,7 +547,7 @@ qboolean Sound_SupportedFileFormat( const char *fileext );
typedef void( *pfnChangeGame )( const char *progname );
qboolean Host_IsQuakeCompatible( void );
void EXPORT Host_Shutdown( void );
void Host_ShutdownWithReason( const char *reason );
int EXPORT Host_Main( int argc, char **argv, const char *progname, int bChangeGame, pfnChangeGame func );
void Host_EndGame( qboolean abort, const char *message, ... ) FORMAT_CHECK( 2 );
void Host_AbortCurrentFrame( void ) NORETURN;
@@ -559,6 +559,7 @@ void Host_Error( const char *error, ... ) FORMAT_CHECK( 1 );
void Host_ValidateEngineFeatures( uint32_t mask, uint32_t features );
void Host_Frame( double time );
void Host_Credits( void );
void Host_ExitInMain( void );
//
// host_state.c
@@ -577,11 +578,19 @@ CLIENT / SERVER SYSTEMS
==============================================================
*/
#if !XASH_DEDICATED
void CL_Init( void );
void CL_Shutdown( void );
void Host_ClientBegin( void );
void Host_ClientFrame( void );
int CL_Active( void );
#else
static inline void CL_Init( void ) { }
static inline void CL_Shutdown( void ) { }
static inline void Host_ClientBegin( void ) { Cbuf_Execute(); }
static inline void Host_ClientFrame( void ) { }
static inline int CL_Active( void ) { return 0; }
#endif
void SV_Init( void );
void SV_Shutdown( const char *finalmsg );
@@ -702,13 +711,32 @@ typedef enum connprotocol_e
struct physent_s;
struct sv_client_s;
typedef struct sizebuf_s sizebuf_t;
int SV_GetMaxClients( void );
#if !XASH_DEDICATED
qboolean CL_Initialized( void );
qboolean CL_IsInGame( void );
qboolean CL_IsInConsole( void );
qboolean CL_IsIntermission( void );
qboolean CL_Initialized( void );
qboolean CL_DisableVisibility( void );
qboolean CL_IsRecordDemo( void );
qboolean CL_IsPlaybackDemo( void );
qboolean UI_CreditsActive( void );
int CL_GetMaxClients( void );
#else
static inline qboolean CL_Initialized( void ) { return false; }
static inline qboolean CL_IsInGame( void ) { return false; }
static inline qboolean CL_IsInConsole( void ) { return false; }
static inline qboolean CL_IsIntermission( void ) { return false; }
static inline qboolean CL_DisableVisibility( void ) { return false; }
static inline qboolean CL_IsRecordDemo( void ) { return false; }
static inline qboolean CL_IsPlaybackDemo( void ) { return false; }
static inline qboolean UI_CreditsActive( void ) { return false; }
static inline int CL_GetMaxClients( void ) { return SV_GetMaxClients(); }
#endif
char *CL_Userinfo( void );
void CL_CharEvent( int key );
qboolean CL_DisableVisibility( void );
byte *COM_LoadFile( const char *filename, int usehunk, int *pLength ) MALLOC_LIKE( free, 1 );
struct cmd_s *Cmd_GetFirstFunctionHandle( void );
struct cmd_s *Cmd_GetNextFunctionHandle( struct cmd_s *cmd );
@@ -726,13 +754,7 @@ const char *CL_MsgInfo( int cmd );
void SV_DrawDebugTriangles( void );
void SV_DrawOrthoTriangles( void );
double CL_GetDemoFramerate( void );
qboolean UI_CreditsActive( void );
void CL_StopPlayback( void );
int CL_GetMaxClients( void );
int SV_GetMaxClients( void );
qboolean CL_IsRecordDemo( void );
qboolean CL_IsTimeDemo( void );
qboolean CL_IsPlaybackDemo( void );
qboolean SV_Initialized( void );
void CL_ProcessFile( qboolean successfully_received, const char *filename );
int SV_GetSaveComment( const char *savename, char *comment );

View File

@@ -1395,7 +1395,7 @@ void Host_WriteConfig( void )
{
Con_Reportf( "%s()\n", __func__ );
FS_Printf( f, "//=======================================================================\n");
FS_Printf( f, "//\tGenerated by "XASH_ENGINE_NAME" (%i, %s, %s, %s-%s)\n", Q_buildnum(), Q_buildcommit(), Q_buildbranch(), Q_buildos(), Q_buildarch());
FS_Printf( f, "//\tGenerated by "XASH_ENGINE_NAME" (%i, %s, %s, %s-%s)\n", Q_buildnum(), g_buildcommit, g_buildbranch, Q_buildos(), Q_buildarch());
FS_Printf( f, "//\t\tconfig.cfg - archive of cvars\n" );
FS_Printf( f, "//=======================================================================\n" );
Key_WriteBindings( f );
@@ -1443,7 +1443,7 @@ void GAME_EXPORT Host_WriteServerConfig( const char *name )
if(( f = FS_Open( newconfigfile, "w", false )) != NULL )
{
FS_Printf( f, "//=======================================================================\n" );
FS_Printf( f, "//\tGenerated by "XASH_ENGINE_NAME" (%i, %s, %s, %s-%s)\n", Q_buildnum(), Q_buildcommit(), Q_buildbranch(), Q_buildos(), Q_buildarch());
FS_Printf( f, "//\tGenerated by "XASH_ENGINE_NAME" (%i, %s, %s, %s-%s)\n", Q_buildnum(), g_buildcommit, g_buildbranch, Q_buildos(), Q_buildarch());
FS_Printf( f, "//\t\tgame.cfg - multiplayer server temporare config\n" );
FS_Printf( f, "//=======================================================================\n" );
@@ -1478,7 +1478,7 @@ void Host_WriteOpenGLConfig( void )
{
Con_Reportf( "%s()\n", __func__ );
FS_Printf( f, "//=======================================================================\n" );
FS_Printf( f, "//\tGenerated by "XASH_ENGINE_NAME" (%i, %s, %s, %s-%s)\n", Q_buildnum(), Q_buildcommit(), Q_buildbranch(), Q_buildos(), Q_buildarch());
FS_Printf( f, "//\tGenerated by "XASH_ENGINE_NAME" (%i, %s, %s, %s-%s)\n", Q_buildnum(), g_buildcommit, g_buildbranch, Q_buildos(), Q_buildarch());
FS_Printf( f, "//\t\t%s - archive of renderer implementation cvars\n", name );
FS_Printf( f, "//=======================================================================\n" );
FS_Printf( f, "\n" );
@@ -1508,7 +1508,7 @@ void Host_WriteVideoConfig( void )
{
Con_Reportf( "%s()\n", __func__ );
FS_Printf( f, "//=======================================================================\n" );
FS_Printf( f, "//\tGenerated by "XASH_ENGINE_NAME" (%i, %s, %s, %s-%s)\n", Q_buildnum(), Q_buildcommit(), Q_buildbranch(), Q_buildos(), Q_buildarch());
FS_Printf( f, "//\tGenerated by "XASH_ENGINE_NAME" (%i, %s, %s, %s-%s)\n", Q_buildnum(), g_buildcommit, g_buildbranch, Q_buildos(), Q_buildarch());
FS_Printf( f, "//\t\tvideo.cfg - archive of renderer variables\n");
FS_Printf( f, "//=======================================================================\n" );
Cvar_WriteVariables( f, FCVAR_RENDERINFO );
@@ -1534,7 +1534,7 @@ void Key_EnumCmds_f( void )
if( f )
{
FS_Printf( f, "//=======================================================================\n");
FS_Printf( f, "//\tGenerated by "XASH_ENGINE_NAME" (%i, %s, %s, %s-%s)\n", Q_buildnum(), Q_buildcommit(), Q_buildbranch(), Q_buildos(), Q_buildarch());
FS_Printf( f, "//\tGenerated by "XASH_ENGINE_NAME" (%i, %s, %s, %s-%s)\n", Q_buildnum(), g_buildcommit, g_buildbranch, Q_buildos(), Q_buildarch());
FS_Printf( f, "//\t\thelp.txt - xash commands and console variables\n");
FS_Printf( f, "//=======================================================================\n");

View File

@@ -49,52 +49,6 @@ const char *CL_MsgInfo( int cmd )
return sz;
}
int GAME_EXPORT CL_Active( void )
{
return false;
}
qboolean CL_Initialized( void )
{
return false;
}
qboolean CL_IsInGame( void )
{
return true; // always active for dedicated servers
}
qboolean CL_IsInConsole( void )
{
return false;
}
qboolean CL_IsIntermission( void )
{
return false;
}
qboolean CL_IsPlaybackDemo( void )
{
return false;
}
qboolean CL_IsRecordDemo( void )
{
return false;
}
qboolean CL_DisableVisibility( void )
{
return false;
}
void CL_Init( void )
{
}
void Key_Init( void )
{
@@ -130,16 +84,6 @@ void CL_WriteMessageHistory( void )
}
void Host_ClientBegin( void )
{
Cbuf_Execute();
}
void Host_ClientFrame( void )
{
}
void Host_InputFrame( void )
{
}
@@ -159,11 +103,6 @@ void GAME_EXPORT S_StopSound(int entnum, int channel, const char *soundname)
}
int GAME_EXPORT CL_GetMaxClients( void )
{
return 0;
}
void IN_TouchInitConfig( void )
{
@@ -174,11 +113,6 @@ void CL_Disconnect( void )
}
void CL_Shutdown( void )
{
}
void R_ClearStaticEntities( void )
{
@@ -189,11 +123,6 @@ void Host_Credits( void )
}
qboolean UI_CreditsActive( void )
{
return false;
}
void S_StopBackgroundTrack( void )
{
@@ -204,11 +133,6 @@ void SCR_BeginLoadingPlaque( qboolean is_background )
}
int S_GetCurrentDynamicSounds( soundlist_t *pout, int size )
{
return 0;
}
void S_StopAllSounds( qboolean ambient )
{

View File

@@ -84,6 +84,11 @@ static void FS_Path_f_( void )
FS_Path_f();
}
static void FS_MakeGameInfo_f( void )
{
g_fsapi.MakeGameInfo();
}
static const fs_interface_t fs_memfuncs =
{
Con_Printf,
@@ -272,6 +277,7 @@ void FS_Init( const char *basedir )
Cmd_AddRestrictedCommand( "fs_rescan", FS_Rescan_f, "rescan filesystem search pathes" );
Cmd_AddRestrictedCommand( "fs_path", FS_Path_f_, "show filesystem search pathes" );
Cmd_AddRestrictedCommand( "fs_clearpaths", FS_ClearPaths_f, "clear filesystem search pathes" );
Cmd_AddRestrictedCommand( "fs_make_gameinfo", FS_MakeGameInfo_f, "create gameinfo.txt for current running game" );
if( !Sys_GetParmFromCmdLine( "-dll", host.gamedll ))
host.gamedll[0] = 0;

View File

@@ -43,6 +43,27 @@ GNU General Public License for more details.
static pfnChangeGame pChangeGame = NULL;
host_parm_t host; // host parms
#if XASH_ANDROID
static jmp_buf return_from_main_buf;
/*
===============
Host_ExitInMain
On some platforms (e.g. Android) we can't exit with exit(3) as calling it would
kill wrapper process (e.g. app_process) too early, before all resources would
be freed, contexts released, files closed, etc, etc...
To fix this, we create jmp_buf in Host_Main function, when jumping into with
non-zero value will immediately return from it with `error_on_exit`.
===============
*/
void Host_ExitInMain( void )
{
longjmp( return_from_main_buf, 1 );
}
#endif // XASH_ANDROID
#ifdef XASH_ENGINE_TESTS
struct tests_stats_s tests_stats;
#endif
@@ -209,7 +230,7 @@ static void Sys_PrintUsage( const char *exename )
fprintf( stderr, usage_str, exename );
#endif
Sys_Quit();
Sys_Quit( NULL );
}
static void Sys_PrintBugcompUsage( const char *exename )
@@ -234,7 +255,7 @@ static void Sys_PrintBugcompUsage( const char *exename )
fprintf( stderr, usage_str, exename );
#endif
Sys_Quit();
Sys_Quit( NULL );
}
/*
@@ -362,9 +383,8 @@ static void Host_NewInstance( const char *name, const char *finalmsg )
if( !pChangeGame ) return;
host.change_game = true;
Q_strncpy( host.finalmsg, finalmsg, sizeof( host.finalmsg ));
if( !Sys_NewInstance( name ))
if( !Sys_NewInstance( name, finalmsg ))
pChangeGame( name ); // call from hl.exe
}
@@ -418,8 +438,7 @@ static void Host_Exec_f( void )
{
string cfgpath;
byte *f;
char *txt;
fs_offset_t len;
fs_offset_t len;
if( Cmd_Argc() != 2 )
{
@@ -474,20 +493,29 @@ static void Host_Exec_f( void )
return;
}
// len is fs_offset_t, which can be larger than size_t
if( len >= SIZE_MAX )
{
Con_Reportf( "%s: %s is too long\n", __func__, Cmd_Argv( 1 ));
return;
}
if( !Q_stricmp( "config.cfg", cfgpath ))
host.config_executed = true;
// adds \n\0 at end of the file
txt = Z_Calloc( len + 2 );
memcpy( txt, f, len );
txt[len] = '\n';
txt[len + 1] = '\0';
Mem_Free( f );
if( !host.apply_game_config )
Con_Printf( "execing %s\n", Cmd_Argv( 1 ));
Cbuf_InsertText( txt );
Mem_Free( txt );
// adds \n at end of the file
// FS_LoadFile always null terminates
if( f[len - 1] != '\n' )
{
Cbuf_InsertTextLen( f, len, len + 1 );
Cbuf_InsertTextLen( "\n", 1, 1 );
}
else Cbuf_InsertTextLen( f, len, len );
Mem_Free( f );
}
/*
@@ -809,11 +837,11 @@ void GAME_EXPORT Host_Error( const char *error, ... )
}
else
{
Con_Printf( "%s: %s", __func__, hosterror1 );
if( host.allow_console )
{
UI_SetActiveMenu( false );
Key_SetKeyDest( key_console );
Con_Printf( "%s: %s", __func__, hosterror1 );
}
else Platform_MessageBox( "Host Error", hosterror1, true );
}
@@ -830,7 +858,6 @@ void GAME_EXPORT Host_Error( const char *error, ... )
recursive = true;
Q_strncpy( hosterror2, hosterror1, sizeof( hosterror2 ));
host.errorframe = host.framecount; // to avoid multply calls per frame
Q_snprintf( host.finalmsg, sizeof( host.finalmsg ), "Server crashed: %s", hosterror1 );
// clearing cmd buffer to prevent execute any commands
COM_InitHostState();
@@ -915,7 +942,7 @@ static void Host_RunTests( int stage )
#endif
Msg( "Done! %d passed, %d failed\n", tests_stats.passed, tests_stats.failed );
error_on_exit = tests_stats.failed > 0 ? EXIT_FAILURE : EXIT_SUCCESS;
Sys_Quit();
Sys_Quit( NULL );
}
}
#endif
@@ -1162,6 +1189,11 @@ static void Host_FreeCommon( void )
FS_Shutdown();
}
static void Sys_Quit_f( void )
{
Sys_Quit( "command" );
}
/*
=================
Host_Main
@@ -1201,7 +1233,7 @@ int EXPORT Host_Main( int argc, char **argv, const char *progname, int bChangeGa
Cvar_Getf( "buildnum", FCVAR_READ_ONLY, "returns a current build number", "%i", Q_buildnum_compat());
Cvar_Getf( "ver", FCVAR_READ_ONLY, "shows an engine version", "%i/%s (hw build %i)", PROTOCOL_VERSION, XASH_COMPAT_VERSION, Q_buildnum_compat());
Cvar_Getf( "host_ver", FCVAR_READ_ONLY, "detailed info about this build", "%i " XASH_VERSION " %s %s %s", Q_buildnum(), Q_buildos(), Q_buildarch(), Q_buildcommit());
Cvar_Getf( "host_ver", FCVAR_READ_ONLY, "detailed info about this build", "%i " XASH_VERSION " %s %s %s", Q_buildnum(), Q_buildos(), Q_buildarch(), g_buildcommit);
Cvar_Getf( "host_lowmemorymode", FCVAR_READ_ONLY, "indicates if engine compiled for low RAM consumption (0 - normal, 1 - low engine limits, 2 - low protocol limits)", "%i", XASH_LOW_MEMORY );
Mod_Init();
@@ -1236,8 +1268,8 @@ int EXPORT Host_Main( int argc, char **argv, const char *progname, int bChangeGa
// disable texture replacements for dedicated
Cvar_FullSet( "host_allow_materials", "0", FCVAR_READ_ONLY );
Cmd_AddRestrictedCommand( "quit", Sys_Quit, "quit the game" );
Cmd_AddRestrictedCommand( "exit", Sys_Quit, "quit the game" );
Cmd_AddRestrictedCommand( "quit", Sys_Quit_f, "quit the game" );
Cmd_AddRestrictedCommand( "exit", Sys_Quit_f, "quit the game" );
}
else Cmd_AddRestrictedCommand( "minimize", Host_Minimize_f, "minimize main window to tray" );
@@ -1308,6 +1340,11 @@ int EXPORT Host_Main( int argc, char **argv, const char *progname, int bChangeGa
// check after all configs were executed
HPAK_CheckIntegrity( hpk_custom_file.string );
#if XASH_ANDROID
if( setjmp( return_from_main_buf ))
return error_on_exit;
#endif // XASH_ANDROID
// main window message loop
while( !host.crashed )
{
@@ -1320,20 +1357,31 @@ int EXPORT Host_Main( int argc, char **argv, const char *progname, int bChangeGa
return 0;
}
void EXPORT Host_Shutdown( void );
void EXPORT Host_Shutdown( void )
{
Host_ShutdownWithReason( "launcher shutdown" );
}
/*
=================
Host_Shutdown
=================
*/
void EXPORT Host_Shutdown( void )
void Host_ShutdownWithReason( const char *reason )
{
qboolean error = host.status == HOST_ERR_FATAL;
if( host.shutdown_issued ) return;
if( host.shutdown_issued )
return;
host.shutdown_issued = true;
if( host.status != HOST_ERR_FATAL ) host.status = HOST_SHUTDOWN; // prepare host to normal shutdown
if( !host.change_game ) Q_strncpy( host.finalmsg, "Server shutdown", sizeof( host.finalmsg ));
if( reason != NULL )
Con_Printf( S_NOTE "Issuing host shutdown due to reason \"%s\"\n", reason );
if( host.status != HOST_ERR_FATAL )
host.status = HOST_SHUTDOWN; // prepare host to normal shutdown
#if !XASH_DEDICATED
if( host.type == HOST_NORMAL && !error )
@@ -1357,5 +1405,5 @@ void EXPORT Host_Shutdown( void )
// restore filter
Sys_RestoreCrashHandler();
Sys_CloseLog();
Sys_CloseLog( reason );
}

View File

@@ -104,7 +104,7 @@ static void HPAK_CreatePak( const char *filename, resource_t *pResource, byte *p
string pakname;
byte md5[16];
file_t *fout;
MD5Context_t ctx;
MD5Context_t ctx = { 0 };
if( !COM_CheckString( filename ))
return;
@@ -125,7 +125,6 @@ static void HPAK_CreatePak( const char *filename, resource_t *pResource, byte *p
}
// let's hash it.
memset( &ctx, 0, sizeof( MD5Context_t ));
MD5Init( &ctx );
if( pData == NULL )
@@ -214,7 +213,7 @@ void HPAK_AddLump( qboolean bUseQueue, const char *name, resource_t *pResource,
file_t *file_src;
file_t *file_dst;
byte md5[16];
MD5Context_t ctx;
MD5Context_t ctx = { 0 };
if( pData == NULL && pFile == NULL )
return;
@@ -226,7 +225,6 @@ void HPAK_AddLump( qboolean bUseQueue, const char *name, resource_t *pResource,
}
// hash it
memset( &ctx, 0, sizeof( MD5Context_t ));
MD5Init( &ctx );
if( !pData )

View File

@@ -611,7 +611,7 @@ void GAME_EXPORT ID_SetCustomClientID( const char *id )
void ID_Init( void )
{
MD5Context_t hash = {0};
MD5Context_t hash = { 0 };
byte md5[16];
int i;

View File

@@ -37,11 +37,7 @@ static char **szArgv;
static void Sys_ChangeGame( const char *progname )
{
// a1ba: may never be called within engine
// if platform supports execv() function
Q_strncpy( szGameDir, progname, sizeof( szGameDir ));
Host_Shutdown( );
exit( Host_Main( szArgc, szArgv, szGameDir, 1, &Sys_ChangeGame ) );
// stub
}
static int Sys_Start( void )

View File

@@ -385,10 +385,7 @@ void NET_SaveMasters( void )
master_t *m;
if( !ml.modified )
{
Con_Reportf( "Master server list not changed\n" );
return;
}
f = FS_Open( "xashcomm.lst", "w", true );

View File

@@ -385,6 +385,35 @@ static const mlumpinfo_t extlumps[EXTRA_LUMPS] =
},
};
#define BOX_CLIPNODES_INITIALIZER \
{ \
.planenum = 0, \
.children = { CONTENTS_EMPTY, 1 }, \
}, \
{ \
.planenum = 1, \
.children = { 2, CONTENTS_EMPTY }, \
}, \
{ \
.planenum = 2, \
.children = { CONTENTS_EMPTY, 3 }, \
}, \
{ \
.planenum = 3, \
.children = { 4, CONTENTS_EMPTY }, \
}, \
{ \
.planenum = 4, \
.children = { CONTENTS_EMPTY, 5 }, \
}, \
{ \
.planenum = 5, \
.children = { CONTENTS_SOLID, CONTENTS_EMPTY }, \
}, \
const mclipnode16_t box_clipnodes16[6] = { BOX_CLIPNODES_INITIALIZER };
const mclipnode32_t box_clipnodes32[6] = { BOX_CLIPNODES_INITIALIZER };
/*
===============================================================================
@@ -856,7 +885,7 @@ Mod_PointInLeaf
==================
*/
mleaf_t *Mod_PointInLeaf( const vec3_t p, mnode_t *node )
mleaf_t *Mod_PointInLeaf( const vec3_t p, mnode_t *node, model_t *mod )
{
Assert( node != NULL );
@@ -864,7 +893,7 @@ mleaf_t *Mod_PointInLeaf( const vec3_t p, mnode_t *node )
{
if( node->contents < 0 )
return (mleaf_t *)node;
node = node->children[PlaneDiff( p, node->plane ) <= 0];
node = node_child( node, PlaneDiff( p, node->plane ) <= 0, mod );
}
// never reached
@@ -885,7 +914,7 @@ byte *Mod_GetPVSForPoint( const vec3_t p )
ASSERT( worldmodel != NULL );
leaf = Mod_PointInLeaf( p, worldmodel->nodes );
leaf = Mod_PointInLeaf( p, worldmodel->nodes, worldmodel );
if( leaf && leaf->cluster >= 0 )
return Mod_DecompressPVS( leaf->compressed_vis, world.visbytes );
@@ -905,14 +934,14 @@ static void Mod_FatPVS_RecursiveBSPNode( const vec3_t org, float radius, byte *v
float d = PlaneDiff( org, node->plane );
if( d > radius )
node = node->children[0];
node = node_child( node, 0, worldmodel );
else if( d < -radius )
node = node->children[1];
node = node_child( node, 1, worldmodel );
else
{
// go down both sides
Mod_FatPVS_RecursiveBSPNode( org, radius, visbuffer, visbytes, node->children[0], phs );
node = node->children[1];
Mod_FatPVS_RecursiveBSPNode( org, radius, visbuffer, visbytes, node_child( node, 0, worldmodel ), phs );
node = node_child( node, 1, worldmodel );
}
}
@@ -950,7 +979,7 @@ int Mod_FatPVS( const vec3_t org, float radius, byte *visbuffer, int visbytes, q
ASSERT( worldmodel != NULL );
leaf = Mod_PointInLeaf( org, worldmodel->nodes );
leaf = Mod_PointInLeaf( org, worldmodel->nodes, worldmodel );
bytes = Q_min( bytes, visbytes );
// enable full visibility for some reasons
@@ -1009,20 +1038,16 @@ static void Mod_BoxLeafnums_r( leaflist_t *ll, mnode_t *node )
sides = BOX_ON_PLANE_SIDE( ll->mins, ll->maxs, node->plane );
if( sides == 1 )
{
node = node->children[0];
}
node = node_child( node, 0, worldmodel );
else if( sides == 2 )
{
node = node->children[1];
}
node = node_child( node, 1, worldmodel );
else
{
// go down both
if( ll->topnode == -1 )
ll->topnode = node - worldmodel->nodes;
Mod_BoxLeafnums_r( ll, node->children[0] );
node = node->children[1];
Mod_BoxLeafnums_r( ll, node_child( node, 0, worldmodel ));
node = node_child( node, 1, worldmodel );
}
}
}
@@ -1079,35 +1104,6 @@ qboolean Mod_BoxVisible( const vec3_t mins, const vec3_t maxs, const byte *visbi
return false;
}
/*
=============
Mod_HeadnodeVisible
=============
*/
qboolean Mod_HeadnodeVisible( mnode_t *node, const byte *visbits, int *lastleaf )
{
if( !node || node->contents == CONTENTS_SOLID )
return false;
if( node->contents < 0 )
{
if( !CHECKVISBIT( visbits, ((mleaf_t *)node)->cluster ))
return false;
if( lastleaf )
*lastleaf = ((mleaf_t *)node)->cluster;
return true;
}
if( Mod_HeadnodeVisible( node->children[0], visbits, lastleaf ))
return true;
if( Mod_HeadnodeVisible( node->children[1], visbits, lastleaf ))
return true;
return false;
}
/*
=================
Mod_FindModelOrigin
@@ -1264,22 +1260,35 @@ Mod_GetFaceContents
determine face contents by name
==================
*/
static mvertex_t *Mod_GetVertexByNumber( model_t *mod, int surfedge )
static mvertex_t *Mod_GetVertexByNumber( model_t *mod, int surfedge, const dbspmodel_t *bmod )
{
int lindex;
medge_t *edge;
int lindex = mod->surfedges[surfedge];
lindex = mod->surfedges[surfedge];
if( lindex > 0 )
if( bmod->version == QBSP2_VERSION )
{
edge = &mod->edges[lindex];
return &mod->vertexes[edge->v[0]];
if( lindex > 0 )
{
medge32_t *edge = &mod->edges32[lindex];
return &mod->vertexes[edge->v[0]];
}
else
{
medge32_t *edge = &mod->edges32[-lindex];
return &mod->vertexes[edge->v[1]];
}
}
else
{
edge = &mod->edges[-lindex];
return &mod->vertexes[edge->v[1]];
if( lindex > 0 )
{
medge16_t *edge = &mod->edges16[lindex];
return &mod->vertexes[edge->v[0]];
}
else
{
medge16_t *edge = &mod->edges16[-lindex];
return &mod->vertexes[edge->v[1]];
}
}
}
@@ -1364,7 +1373,7 @@ Mod_CalcSurfaceExtents
Fills in surf->texturemins[] and surf->extents[]
=================
*/
static void Mod_CalcSurfaceExtents( model_t *mod, msurface_t *surf )
static void Mod_CalcSurfaceExtents( model_t *mod, msurface_t *surf, const dbspmodel_t *bmod )
{
// this place is VERY critical to precision
// keep it as float, don't use double, because it causes issues with lightmap
@@ -1391,8 +1400,16 @@ static void Mod_CalcSurfaceExtents( model_t *mod, msurface_t *surf )
if( e >= mod->numedges || e <= -mod->numedges )
Host_Error( "%s: bad edge\n", __func__ );
if( e >= 0 ) v = &mod->vertexes[mod->edges[e].v[0]];
else v = &mod->vertexes[mod->edges[-e].v[1]];
if( bmod->version == QBSP2_VERSION )
{
if( e >= 0 ) v = &mod->vertexes[mod->edges32[e].v[0]];
else v = &mod->vertexes[mod->edges32[-e].v[1]];
}
else
{
if( e >= 0 ) v = &mod->vertexes[mod->edges16[e].v[0]];
else v = &mod->vertexes[mod->edges16[-e].v[1]];
}
for( j = 0; j < 2; j++ )
{
@@ -1446,7 +1463,7 @@ Mod_CalcSurfaceBounds
fills in surf->mins and surf->maxs
=================
*/
static void Mod_CalcSurfaceBounds( model_t *mod, msurface_t *surf )
static void Mod_CalcSurfaceBounds( model_t *mod, msurface_t *surf, const dbspmodel_t *bmod )
{
int i, e;
mvertex_t *v;
@@ -1460,8 +1477,16 @@ static void Mod_CalcSurfaceBounds( model_t *mod, msurface_t *surf )
if( e >= mod->numedges || e <= -mod->numedges )
Host_Error( "%s: bad edge\n", __func__ );
if( e >= 0 ) v = &mod->vertexes[mod->edges[e].v[0]];
else v = &mod->vertexes[mod->edges[-e].v[1]];
if( bmod->version == QBSP2_VERSION )
{
if( e >= 0 ) v = &mod->vertexes[mod->edges32[e].v[0]];
else v = &mod->vertexes[mod->edges32[-e].v[1]];
}
else
{
if( e >= 0 ) v = &mod->vertexes[mod->edges16[e].v[0]];
else v = &mod->vertexes[mod->edges16[-e].v[1]];
}
AddPointToBounds( v->position, surf->info->mins, surf->info->maxs );
}
@@ -1473,7 +1498,7 @@ static void Mod_CalcSurfaceBounds( model_t *mod, msurface_t *surf )
Mod_CreateFaceBevels
=================
*/
static void Mod_CreateFaceBevels( model_t *mod, msurface_t *surf )
static void Mod_CreateFaceBevels( model_t *mod, msurface_t *surf, const dbspmodel_t *bmod )
{
vec3_t delta, edgevec;
byte *facebevel;
@@ -1506,8 +1531,8 @@ static void Mod_CreateFaceBevels( model_t *mod, msurface_t *surf )
{
mplane_t *dest = &fb->edges[i];
v0 = Mod_GetVertexByNumber( mod, surf->firstedge + i );
v1 = Mod_GetVertexByNumber( mod, surf->firstedge + (i + 1) % surf->numedges );
v0 = Mod_GetVertexByNumber( mod, surf->firstedge + i, bmod );
v1 = Mod_GetVertexByNumber( mod, surf->firstedge + (i + 1) % surf->numedges, bmod );
VectorSubtract( v1->position, v0->position, edgevec );
CrossProduct( faceNormal, edgevec, dest->normal );
VectorNormalize( dest->normal );
@@ -1521,7 +1546,7 @@ static void Mod_CreateFaceBevels( model_t *mod, msurface_t *surf )
// compute face radius
for( i = 0; i < surf->numedges; i++ )
{
v0 = Mod_GetVertexByNumber( mod, surf->firstedge + i );
v0 = Mod_GetVertexByNumber( mod, surf->firstedge + i, bmod );
VectorSubtract( v0->position, fb->origin, delta );
radius = DotProduct( delta, delta );
fb->radius = Q_max( radius, fb->radius );
@@ -1533,13 +1558,15 @@ static void Mod_CreateFaceBevels( model_t *mod, msurface_t *surf )
Mod_SetParent
=================
*/
static void Mod_SetParent( mnode_t *node, mnode_t *parent )
static void Mod_SetParent( model_t *mod, mnode_t *node, mnode_t *parent )
{
node->parent = parent;
if( node->contents < 0 ) return; // it's leaf
Mod_SetParent( node->children[0], node );
Mod_SetParent( node->children[1], node );
if( node->contents < 0 )
return; // it's leaf
Mod_SetParent( mod, node_child( node, 0, mod ), node );
Mod_SetParent( mod, node_child( node, 1, mod ), node );
}
/*
@@ -1547,7 +1574,7 @@ static void Mod_SetParent( mnode_t *node, mnode_t *parent )
CountClipNodes_r
==================
*/
static void CountClipNodes_r( mclipnode_t *src, hull_t *hull, int nodenum )
static void CountClipNodes16_r( mclipnode16_t *src, hull_t *hull, int nodenum )
{
// leaf?
if( nodenum < 0 ) return;
@@ -1556,16 +1583,11 @@ static void CountClipNodes_r( mclipnode_t *src, hull_t *hull, int nodenum )
Host_Error( "MAX_MAP_CLIPNODES limit exceeded\n" );
hull->lastclipnode++;
CountClipNodes_r( src, hull, src[nodenum].children[0] );
CountClipNodes_r( src, hull, src[nodenum].children[1] );
CountClipNodes16_r( src, hull, src[nodenum].children[0] );
CountClipNodes16_r( src, hull, src[nodenum].children[1] );
}
/*
==================
CountClipNodes32_r
==================
*/
static void CountClipNodes32_r( dclipnode32_t *src, hull_t *hull, int nodenum )
static void CountClipNodes32_r( mclipnode32_t *src, hull_t *hull, int nodenum )
{
// leaf?
if( nodenum < 0 ) return;
@@ -1578,15 +1600,27 @@ static void CountClipNodes32_r( dclipnode32_t *src, hull_t *hull, int nodenum )
CountClipNodes32_r( src, hull, src[nodenum].children[1] );
}
static void CountDClipNodes_r( dclipnode32_t *src, hull_t *hull, int nodenum )
{
// leaf?
if( nodenum < 0 ) return;
if( hull->lastclipnode == MAX_MAP_CLIPNODES )
Host_Error( "MAX_MAP_CLIPNODES limit exceeded\n" );
hull->lastclipnode++;
CountDClipNodes_r( src, hull, src[nodenum].children[0] );
CountDClipNodes_r( src, hull, src[nodenum].children[1] );
}
/*
==================
RemapClipNodes_r
==================
*/
static int RemapClipNodes_r( dclipnode32_t *srcnodes, hull_t *hull, int nodenum )
static int RemapClipNodes_r( dbspmodel_t *bmod, dclipnode32_t *srcnodes, hull_t *hull, int nodenum )
{
dclipnode32_t *src;
mclipnode_t *out;
dclipnode32_t *src;
int i, c;
// leaf?
@@ -1599,13 +1633,22 @@ static int RemapClipNodes_r( dclipnode32_t *srcnodes, hull_t *hull, int nodenum
src = srcnodes + nodenum;
c = hull->lastclipnode;
out = &hull->clipnodes[c];
hull->lastclipnode++;
out->planenum = src->planenum;
for( i = 0; i < 2; i++ )
out->children[i] = RemapClipNodes_r( srcnodes, hull, src->children[i] );
if( bmod->version == QBSP2_VERSION )
{
mclipnode32_t *out = &hull->clipnodes32[c];
out->planenum = src->planenum;
for( i = 0; i < 2; i++ )
out->children[i] = RemapClipNodes_r( bmod, srcnodes, hull, src->children[i] );
}
else
{
mclipnode16_t *out = &hull->clipnodes16[c];
out->planenum = src->planenum;
for( i = 0; i < 2; i++ )
out->children[i] = RemapClipNodes_r( bmod, srcnodes, hull, src->children[i] );
}
return c;
}
@@ -1617,34 +1660,64 @@ Mod_MakeHull0
Duplicate the drawing hull structure as a clipping hull
=================
*/
static void Mod_MakeHull0( model_t *mod )
static void Mod_MakeHull0( model_t *mod, const dbspmodel_t *bmod )
{
mnode_t *in, *child;
mclipnode_t *out;
hull_t *hull;
int i, j;
hull = &mod->hulls[0];
hull->clipnodes = out = Mem_Malloc( mod->mempool, mod->numnodes * sizeof( *out ));
in = mod->nodes;
hull_t *hull = &mod->hulls[0];
int i;
hull->firstclipnode = 0;
hull->lastclipnode = mod->numnodes - 1;
hull->planes = mod->planes;
for( i = 0; i < mod->numnodes; i++, out++, in++ )
if( bmod->version == QBSP2_VERSION )
{
out->planenum = in->plane - mod->planes;
mclipnode32_t *out;
mnode_t *in = mod->nodes;
for( j = 0; j < 2; j++ )
hull->clipnodes32 = out = Mem_Malloc( mod->mempool, mod->numnodes * sizeof( *hull->clipnodes32 ));
for( i = 0; i < mod->numnodes; i++, out++, in++ )
{
child = in->children[j];
int j;
if( child->contents < 0 )
out->children[j] = child->contents;
else out->children[j] = child - mod->nodes;
out->planenum = in->plane - mod->planes;
for( j = 0; j < 2; j++ )
{
mnode_t *child = node_child( in, j, mod );
if( child->contents < 0 )
out->children[j] = child->contents;
else
out->children[j] = child - mod->nodes;
}
}
}
else
{
mclipnode16_t *out;
mnode_t *in = mod->nodes;
hull->clipnodes16 = out = Mem_Malloc( mod->mempool, mod->numnodes * sizeof( *hull->clipnodes16 ));
for( i = 0; i < mod->numnodes; i++, out++, in++ )
{
int j;
out->planenum = in->plane - mod->planes;
for( j = 0; j < 2; j++ )
{
mnode_t *child = node_child( in, j, mod );
if( child->contents < 0 )
out->children[j] = child->contents;
else
out->children[j] = child - mod->nodes;
}
}
}
}
/*
@@ -1688,15 +1761,18 @@ static void Mod_SetupHull( dbspmodel_t *bmod, model_t *mod, poolhandle_t mempool
if( VectorIsNull( hull->clip_mins ) && VectorIsNull( hull->clip_maxs ))
return; // no hull specified
CountClipNodes32_r( bmod->clipnodes_out, hull, headnode );
CountDClipNodes_r( bmod->clipnodes_out, hull, headnode );
// fit array to real count
hull->clipnodes = (mclipnode_t *)Mem_Malloc( mempool, sizeof( mclipnode_t ) * hull->lastclipnode );
if( bmod->version == QBSP2_VERSION )
hull->clipnodes32 = Mem_Malloc( mempool, sizeof( *hull->clipnodes32 ) * hull->lastclipnode );
else
hull->clipnodes16 = Mem_Malloc( mempool, sizeof( *hull->clipnodes16 ) * hull->lastclipnode );
hull->planes = mod->planes; // share planes
hull->lastclipnode = 0; // restart counting
// remap clipnodes to 16-bit indexes
RemapClipNodes_r( bmod->clipnodes_out, hull, headnode );
RemapClipNodes_r( bmod, bmod->clipnodes_out, hull, headnode ); // remap clipnodes to 16-bit indexes
}
static qboolean Mod_LoadLitfile( model_t *mod, const char *ext, size_t expected_size, color24 **out, size_t *outsize )
@@ -1704,7 +1780,8 @@ static qboolean Mod_LoadLitfile( model_t *mod, const char *ext, size_t expected_
char modelname[64], path[64];
int iCompare;
fs_offset_t datasize;
byte *in;
file_t *f;
uint hdr[2];
COM_FileBase( mod->name, modelname, sizeof( modelname ));
Q_snprintf( path, sizeof( path ), "maps/%s.%s", modelname, ext );
@@ -1715,31 +1792,15 @@ static qboolean Mod_LoadLitfile( model_t *mod, const char *ext, size_t expected_
if( iCompare < 0 ) // this may happens if level-designer used -onlyents key for hlcsg
Con_Printf( S_WARN "%s probably is out of date\n", path );
in = FS_LoadFile( path, &datasize, false );
f = FS_Open( path, "rb", false );
if( !in )
if( !f )
{
Con_Printf( S_ERROR "couldn't load %s\n", path );
return false;
}
if( datasize <= 8 ) // header + version
{
Con_Printf( S_ERROR "%s is too short\n", path );
goto cleanup_and_error;
}
if( LittleLong( ((uint *)in)[0] ) != IDDELUXEMAPHEADER )
{
Con_Printf( S_ERROR "%s is corrupted\n", path );
goto cleanup_and_error;
}
if( LittleLong( ((uint *)in)[1] ) != DELUXEMAP_VERSION )
{
Con_Printf( S_ERROR "has %s mismatched version (%u should be %u)\n", path, LittleLong( ((uint *)in)[1] ), DELUXEMAP_VERSION );
goto cleanup_and_error;
}
datasize = FS_FileLength( f );
// skip header bytes
datasize -= 8;
@@ -1750,14 +1811,33 @@ static qboolean Mod_LoadLitfile( model_t *mod, const char *ext, size_t expected_
goto cleanup_and_error;
}
if( FS_Read( f, hdr, sizeof( hdr )) != sizeof( hdr ))
{
Con_Printf( S_ERROR "failed reading header from %s\n", path );
goto cleanup_and_error;
}
if( LittleLong( hdr[0] ) != IDDELUXEMAPHEADER )
{
Con_Printf( S_ERROR "%s is corrupted\n", path );
goto cleanup_and_error;
}
if( LittleLong( hdr[1] ) != DELUXEMAP_VERSION )
{
Con_Printf( S_ERROR "has %s mismatched version (%u should be %u)\n", path, LittleLong( hdr[1] ), DELUXEMAP_VERSION );
goto cleanup_and_error;
}
*out = Mem_Malloc( mod->mempool, datasize );
memcpy( *out, in + 8, datasize );
*outsize = datasize;
Mem_Free( in );
FS_Read( f, *out, datasize );
FS_Close( f );
return true;
cleanup_and_error:
Mem_Free( in );
FS_Close( f );
return false;
}
@@ -1772,6 +1852,7 @@ for embedded submodels
static void Mod_SetupSubmodels( model_t *mod, dbspmodel_t *bmod )
{
qboolean colored = false;
qboolean qbsp2 = false;
poolhandle_t mempool;
char *ents;
dmodel_t *bm;
@@ -1783,6 +1864,9 @@ static void Mod_SetupSubmodels( model_t *mod, dbspmodel_t *bmod )
if( FBitSet( mod->flags, MODEL_COLORED_LIGHTING ))
colored = true;
if( FBitSet( mod->flags, MODEL_QBSP2 ))
qbsp2 = true;
mod->numframes = 2; // regular and alternate animation
// set up the submodels
@@ -1795,7 +1879,10 @@ static void Mod_SetupSubmodels( model_t *mod, dbspmodel_t *bmod )
mod->hulls[0].lastclipnode = bm->headnode[0]; // need to be real count
// counting a real number of clipnodes per each submodel
CountClipNodes_r( mod->hulls[0].clipnodes, &mod->hulls[0], bm->headnode[0] );
if( bmod->version == QBSP2_VERSION )
CountClipNodes32_r( mod->hulls[0].clipnodes32, &mod->hulls[0], bm->headnode[0] );
else
CountClipNodes16_r( mod->hulls[0].clipnodes16, &mod->hulls[0], bm->headnode[0] );
// but hulls1-3 is build individually for a each given submodel
for( j = 1; j < MAX_MAP_HULLS; j++ )
@@ -1813,6 +1900,7 @@ static void Mod_SetupSubmodels( model_t *mod, dbspmodel_t *bmod )
// this bit will be shared between all the submodels include worldmodel
if( colored ) SetBits( mod->flags, MODEL_COLORED_LIGHTING );
if( qbsp2 ) SetBits( mod->flags, MODEL_QBSP2 );
if( i != 0 )
{
@@ -2135,15 +2223,15 @@ Mod_LoadEdges
*/
static void Mod_LoadEdges( model_t *mod, dbspmodel_t *bmod )
{
medge_t *out;
int i;
mod->edges = out = Mem_Malloc( mod->mempool, bmod->numedges * sizeof( medge_t ));
mod->numedges = bmod->numedges;
if( bmod->version == QBSP2_VERSION )
{
dedge32_t *in = (dedge32_t *)bmod->edges32;
dedge32_t *in = bmod->edges32;
medge32_t *out;
mod->edges32 = out = Mem_Malloc( mod->mempool, bmod->numedges * sizeof( *out ));
for( i = 0; i < bmod->numedges; i++, in++, out++ )
{
@@ -2153,7 +2241,9 @@ static void Mod_LoadEdges( model_t *mod, dbspmodel_t *bmod )
}
else
{
dedge_t *in = (dedge_t *)bmod->edges;
dedge_t *in = bmod->edges;
medge16_t *out;
mod->edges16 = out = Mem_Malloc( mod->mempool, bmod->numedges * sizeof( *out ));
for( i = 0; i < bmod->numedges; i++, in++, out++ )
{
@@ -2913,9 +3003,9 @@ static void Mod_LoadSurfaces( model_t *mod, dbspmodel_t *bmod )
if( FBitSet( out->texinfo->flags, TEX_SPECIAL ))
SetBits( out->flags, SURF_DRAWTILED );
Mod_CalcSurfaceBounds( mod, out );
Mod_CalcSurfaceExtents( mod, out );
Mod_CreateFaceBevels( mod, out );
Mod_CalcSurfaceBounds( mod, out, bmod );
Mod_CalcSurfaceExtents( mod, out, bmod );
Mod_CreateFaceBevels( mod, out, bmod );
// grab the second sample to detect colored lighting
if( test_lightsize > 0 && lightofs != -1 )
@@ -2962,7 +3052,6 @@ static void Mod_LoadSurfaces( model_t *mod, dbspmodel_t *bmod )
if( samples == 1 || samples == 3 )
{
bmod->lightmap_samples = (int)samples;
Con_Reportf( "lighting: %s\n", (bmod->lightmap_samples == 1) ? "monochrome" : "colored" );
bmod->lightmap_samples = Q_max( bmod->lightmap_samples, 1 ); // avoid division by zero
}
else Con_DPrintf( S_WARN "lighting invalid samplecount: %g, defaulting to %i\n", samples, bmod->lightmap_samples );
@@ -2994,16 +3083,62 @@ static void Mod_LoadNodes( model_t *mod, dbspmodel_t *bmod )
out->minmaxs[j+3] = in->maxs[j];
}
#if !XASH_64BIT
if( in->firstface >= BIT( 24 ))
{
Host_Error( "%s: face index limit exceeded on node %i\n", __func__, i );
return;
}
if( in->numfaces >= BIT( 24 ))
{
Host_Error( "%s: face count limit exceeded on node %i\n", __func__, i );
return;
}
#endif
p = in->planenum;
out->plane = mod->planes + p;
out->firstsurface = in->firstface;
out->numsurfaces = in->numfaces;
out->firstsurface_0 = in->firstface & 0xFFFF;
out->numsurfaces_0 = in->numfaces & 0xFFFF;
out->firstsurface_1 = in->firstface >> 16;
out->numsurfaces_1 = in->numfaces >> 16;
for( j = 0; j < 2; j++ )
{
p = in->children[j];
if( p >= 0 ) out->children[j] = mod->nodes + p;
else out->children[j] = (mnode_t *)(mod->leafs + ( -1 - p ));
#if XASH_64BIT
if( p >= 0 ) out->children_[j] = mod->nodes + p;
else out->children_[j] = (mnode_t *)(mod->leafs + ( -1 - p ));
#else
if( j == 0 )
{
if( p >= 0 )
{
out->child_0_leaf = 0;
out->child_0_off = p;
}
else
{
out->child_0_leaf = 1;
out->child_0_off = -1 - p;
}
}
else
{
if( p >= 0 )
{
out->child_1_leaf = 0;
out->child_1_off = p;
}
else
{
out->child_1_leaf = 1;
out->child_1_off = -1 - p;
}
}
#endif
}
}
else
@@ -3018,20 +3153,20 @@ static void Mod_LoadNodes( model_t *mod, dbspmodel_t *bmod )
p = in->planenum;
out->plane = mod->planes + p;
out->firstsurface = in->firstface;
out->numsurfaces = in->numfaces;
out->firstsurface_0 = in->firstface;
out->numsurfaces_0 = in->numfaces;
for( j = 0; j < 2; j++ )
{
p = in->children[j];
if( p >= 0 ) out->children[j] = mod->nodes + p;
else out->children[j] = (mnode_t *)(mod->leafs + ( -1 - p ));
if( p >= 0 ) out->children_[j] = mod->nodes + p;
else out->children_[j] = (mnode_t *)(mod->leafs + ( -1 - p ));
}
}
}
// sets nodes and leafs
Mod_SetParent( mod->nodes, NULL );
Mod_SetParent( mod, mod->nodes, NULL );
}
/*
@@ -3413,6 +3548,8 @@ static void Mod_LoadLighting( model_t *mod, dbspmodel_t *bmod )
break;
}
Con_Reportf( "lighting: %s\n", FBitSet( mod->flags, MODEL_COLORED_LIGHTING ) ? "colored" : "monochrome" );
// not supposed to be load ?
if( FBitSet( host.features, ENGINE_LOAD_DELUXEDATA ))
{
@@ -3491,13 +3628,6 @@ static qboolean Mod_LoadBmodelLumps( model_t *mod, const byte *mod_base, qboolea
Q_strncpy( loadstat.name, mod->name, sizeof( loadstat.name ));
wadvalue[0] = '\0';
#ifndef SUPPORT_BSP2_FORMAT
if( header->version == QBSP2_VERSION )
{
Con_Printf( S_ERROR DEFAULT_BSP_BUILD_ERROR, mod->name );
return false;
}
#endif
switch( header->version )
{
case HLBSP_VERSION:
@@ -3520,6 +3650,9 @@ static qboolean Mod_LoadBmodelLumps( model_t *mod, const byte *mod_base, qboolea
// everything else
srclumps[0].lumpnumber = LUMP_ENTITIES;
srclumps[1].lumpnumber = LUMP_PLANES;
if( header->version == QBSP2_VERSION )
SetBits( mod->flags, MODEL_QBSP2 );
break;
default:
Con_Printf( S_ERROR "%s has wrong version number (%i should be %i)\n", mod->name, header->version, HLBSP_VERSION );
@@ -3570,7 +3703,7 @@ static qboolean Mod_LoadBmodelLumps( model_t *mod, const byte *mod_base, qboolea
Mod_LoadClipnodes( mod, bmod );
// preform some post-initalization
Mod_MakeHull0( mod );
Mod_MakeHull0( mod, bmod );
Mod_SetupSubmodels( mod, bmod );
if( isworld )
@@ -3660,15 +3793,6 @@ qboolean Mod_TestBmodelLumps( file_t *f, const char *name, const byte *mod_base,
if( silent )
SetBits( flags, LUMP_SILENT );
#ifndef SUPPORT_BSP2_FORMAT
if( header->version == QBSP2_VERSION )
{
if( !FBitSet( flags, LUMP_SILENT ))
Con_Printf( S_ERROR DEFAULT_BSP_BUILD_ERROR, name );
return false;
}
#endif
switch( header->version )
{
case HLBSP_VERSION:

View File

@@ -135,6 +135,8 @@ extern poolhandle_t com_studiocache;
extern convar_t mod_studiocache;
extern convar_t r_wadtextures;
extern convar_t r_showhull;
extern const mclipnode16_t box_clipnodes16[6];
extern const mclipnode32_t box_clipnodes32[6];
//
// model.c
@@ -167,13 +169,12 @@ void Mod_LoadAliasModel( model_t *mod, const void *buffer, qboolean *loaded );
//
void Mod_LoadBrushModel( model_t *mod, const void *buffer, qboolean *loaded );
qboolean Mod_TestBmodelLumps( file_t *f, const char *name, const byte *mod_base, qboolean silent, dlump_t *entities );
qboolean Mod_HeadnodeVisible( mnode_t *node, const byte *visbits, int *lastleaf );
int Mod_FatPVS( const vec3_t org, float radius, byte *visbuffer, int visbytes, qboolean merge, qboolean fullvis, qboolean false );
qboolean Mod_BoxVisible( const vec3_t mins, const vec3_t maxs, const byte *visbits );
int Mod_CheckLump( const char *filename, const int lump, int *lumpsize );
int Mod_ReadLump( const char *filename, const int lump, void **lumpdata, int *lumpsize );
int Mod_SaveLump( const char *filename, const int lump, void *lumpdata, int lumpsize );
mleaf_t *Mod_PointInLeaf( const vec3_t p, mnode_t *node );
mleaf_t *Mod_PointInLeaf( const vec3_t p, mnode_t *node, model_t *mod );
int Mod_SampleSizeForFace( const msurface_t *surf );
byte *Mod_GetPVSForPoint( const vec3_t p );
void Mod_UnloadBrushModel( model_t *mod );

View File

@@ -50,9 +50,8 @@ static matrix3x4 studio_bones[MAXSTUDIOBONES];
static uint studio_hull_hitgroup[MAXSTUDIOBONES];
static uint cache_hull_hitgroup[MAXSTUDIOBONES];
static mstudiocache_t cache_studio[STUDIO_CACHESIZE];
static mclipnode_t studio_clipnodes[6];
static mplane_t studio_planes[768];
static mplane_t cache_planes[768];
static mplane_t studio_planes[MAXSTUDIOBONES * 6];
static mplane_t cache_planes[MAXSTUDIOBONES * 6];
// current cache state
static int cache_current;
@@ -66,25 +65,14 @@ Mod_InitStudioHull
*/
void Mod_InitStudioHull( void )
{
int i, side;
int i;
if( studio_hull[0].planes != NULL )
return; // already initailized
for( i = 0; i < 6; i++ )
{
studio_clipnodes[i].planenum = i;
side = i & 1;
studio_clipnodes[i].children[side] = CONTENTS_EMPTY;
if( i != 5 ) studio_clipnodes[i].children[side^1] = i + 1;
else studio_clipnodes[i].children[side^1] = CONTENTS_SOLID;
}
for( i = 0; i < MAXSTUDIOBONES; i++ )
{
studio_hull[i].clipnodes = studio_clipnodes;
studio_hull[i].clipnodes16 = (mclipnode16_t *)box_clipnodes16;
studio_hull[i].planes = &studio_planes[i*6];
studio_hull[i].firstclipnode = 0;
studio_hull[i].lastclipnode = 5;
@@ -270,6 +258,11 @@ hull_t *Mod_HullForStudio( model_t *model, float frame, int sequence, vec3_t ang
for( i = j = 0; i < mod_studiohdr->numhitboxes; i++, j += 6 )
{
if( world.version == QBSP2_VERSION )
studio_hull[i].clipnodes32 = (mclipnode32_t *)box_clipnodes32;
else
studio_hull[i].clipnodes16 = (mclipnode16_t *)box_clipnodes16;
if( bSkipShield && i == 21 )
continue; // CS stuff

View File

@@ -326,7 +326,7 @@ static int HTTP_FileConnect( httpfile_t *file )
if( !COM_CheckStringEmpty( http_useragent.string ) || !Q_strcmp( http_useragent.string, "xash3d" ))
{
Q_snprintf( useragent, sizeof( useragent ), "%s/%s (%s-%s; build %d; %s)",
XASH_ENGINE_NAME, XASH_VERSION, Q_buildos( ), Q_buildarch( ), Q_buildnum( ), Q_buildcommit( ));
XASH_ENGINE_NAME, XASH_VERSION, Q_buildos( ), Q_buildarch( ), Q_buildnum( ), g_buildcommit );
}
else Q_strncpy( useragent, http_useragent.string, sizeof( useragent ));

View File

@@ -112,6 +112,9 @@ msurface_t *PM_RecursiveSurfCheck( model_t *mod, mnode_t *node, vec3_t p1, vec3_
int i, side;
msurface_t *surf;
vec3_t mid;
mnode_t *children[2];
int numsurfaces, firstsurface;
loc0:
if( node->contents < 0 )
return NULL;
@@ -119,15 +122,17 @@ loc0:
t1 = PlaneDiff( p1, node->plane );
t2 = PlaneDiff( p2, node->plane );
node_children( children, node, mod );
if( t1 >= -FRAC_EPSILON && t2 >= -FRAC_EPSILON )
{
node = node->children[0];
node = children[0];
goto loc0;
}
if( t1 < FRAC_EPSILON && t2 < FRAC_EPSILON )
{
node = node->children[1];
node = children[1];
goto loc0;
}
@@ -137,13 +142,15 @@ loc0:
VectorLerp( p1, frac, p2, mid );
if(( surf = PM_RecursiveSurfCheck( mod, node->children[side], p1, mid )) != NULL )
if(( surf = PM_RecursiveSurfCheck( mod, children[side], p1, mid )) != NULL )
return surf;
// walk through real faces
for( i = 0; i < node->numsurfaces; i++ )
numsurfaces = node_numsurfaces( node, mod );
firstsurface = node_firstsurface( node, mod );
for( i = 0; i < numsurfaces; i++ )
{
msurface_t *surf = &mod->surfaces[node->firstsurface + i];
msurface_t *surf = &mod->surfaces[firstsurface + i];
mextrasurf_t *info = surf->info;
mfacebevel_t *fb = info->bevel;
int j, contents;
@@ -172,7 +179,7 @@ loc0:
return NULL; // through the fence
}
return PM_RecursiveSurfCheck( mod, node->children[side^1], mid, p2 );
return PM_RecursiveSurfCheck( mod, children[side^1], mid, p2 );
}
/*
@@ -227,6 +234,9 @@ static int PM_TestLine_r( model_t *mod, mnode_t *node, vec_t p1f, vec_t p2f, con
float frac, midf;
int i, r, side;
vec3_t mid;
mnode_t *children[2];
int numsurfaces, firstsurface;
loc0:
if( node->contents < 0 )
{
@@ -242,15 +252,17 @@ loc0:
front = PlaneDiff( start, node->plane );
back = PlaneDiff( stop, node->plane );
node_children( children, node, mod );
if( front >= -FRAC_EPSILON && back >= -FRAC_EPSILON )
{
node = node->children[0];
node = children[0];
goto loc0;
}
if( front < FRAC_EPSILON && back < FRAC_EPSILON )
{
node = node->children[1];
node = children[1];
goto loc0;
}
@@ -261,7 +273,7 @@ loc0:
VectorLerp( start, frac, stop, mid );
midf = p1f + ( p2f - p1f ) * frac;
r = PM_TestLine_r( mod, node->children[side], p1f, midf, start, mid, trace );
r = PM_TestLine_r( mod, children[side], p1f, midf, start, mid, trace );
if( r != CONTENTS_EMPTY )
{
@@ -272,9 +284,11 @@ loc0:
}
// walk through real faces
for( i = 0; i < node->numsurfaces; i++ )
numsurfaces = node_numsurfaces( node, mod );
firstsurface = node_firstsurface( node, mod );
for( i = 0; i < numsurfaces; i++ )
{
msurface_t *surf = &mod->surfaces[node->firstsurface + i];
msurface_t *surf = &mod->surfaces[firstsurface + i];
mextrasurf_t *info = surf->info;
mfacebevel_t *fb = info->bevel;
int j, contents;
@@ -308,7 +322,7 @@ loc0:
return contents;
}
return PM_TestLine_r( mod, node->children[!side], midf, p2f, mid, stop, trace );
return PM_TestLine_r( mod, children[!side], midf, p2f, mid, stop, trace );
}
int PM_TestLineExt( playermove_t *pmove, physent_t *ents, int numents, const vec3_t start, const vec3_t end, int flags )

View File

@@ -25,8 +25,7 @@ GNU General Public License for more details.
#define PM_AllowHitBoxTrace( model, hull ) ( model && model->type == mod_studio && ( FBitSet( model->flags, STUDIO_TRACE_HITBOX ) || hull == 2 ))
static mplane_t pm_boxplanes[6];
static mclipnode_t pm_boxclipnodes[6];
static hull_t pm_boxhull;
static hull_t pm_boxhull;
// default hullmins
static const vec3_t pm_hullmins[MAX_MAP_HULLS] =
@@ -65,23 +64,15 @@ can just be stored out and get a proper hull_t structure.
*/
void PM_InitBoxHull( void )
{
int i, side;
int i;
pm_boxhull.clipnodes = pm_boxclipnodes;
pm_boxhull.clipnodes16 = (mclipnode16_t *)box_clipnodes16;
pm_boxhull.planes = pm_boxplanes;
pm_boxhull.firstclipnode = 0;
pm_boxhull.lastclipnode = 5;
for( i = 0; i < 6; i++ )
{
pm_boxclipnodes[i].planenum = i;
side = i & 1;
pm_boxclipnodes[i].children[side] = CONTENTS_EMPTY;
if( i != 5 ) pm_boxclipnodes[i].children[side^1] = i + 1;
else pm_boxclipnodes[i].children[side^1] = CONTENTS_SOLID;
pm_boxplanes[i].type = i>>1;
pm_boxplanes[i].normal[i>>1] = 1.0f;
pm_boxplanes[i].signbits = 0;
@@ -106,6 +97,11 @@ static hull_t *PM_HullForBox( const vec3_t mins, const vec3_t maxs )
pm_boxplanes[4].dist = maxs[2];
pm_boxplanes[5].dist = mins[2];
if( world.version == QBSP2_VERSION )
pm_boxhull.clipnodes32 = (mclipnode32_t *)box_clipnodes32;
else
pm_boxhull.clipnodes16 = (mclipnode16_t *)box_clipnodes16;
return &pm_boxhull;
}
@@ -122,10 +118,21 @@ int GAME_EXPORT PM_HullPointContents( hull_t *hull, int num, const vec3_t p )
if( !hull || !hull->planes ) // fantom bmodels?
return CONTENTS_NONE;
while( num >= 0 )
if( world.version == QBSP2_VERSION )
{
plane = &hull->planes[hull->clipnodes[num].planenum];
num = hull->clipnodes[num].children[PlaneDiff( p, plane ) < 0];
while( num >= 0 )
{
plane = &hull->planes[hull->clipnodes32[num].planenum];
num = hull->clipnodes32[num].children[PlaneDiff( p, plane ) < 0];
}
}
else
{
while( num >= 0 )
{
plane = &hull->planes[hull->clipnodes16[num].planenum];
num = hull->clipnodes16[num].children[PlaneDiff( p, plane ) < 0];
}
}
return num;
}
@@ -193,7 +200,7 @@ PM_RecursiveHullCheck
*/
qboolean PM_RecursiveHullCheck( hull_t *hull, int num, float p1f, float p2f, vec3_t p1, vec3_t p2, pmtrace_t *trace )
{
mclipnode_t *node;
int children[2];
mplane_t *plane;
float t1, t2;
float frac, midf;
@@ -226,21 +233,31 @@ loc0:
Host_Error( "%s: bad node number %i\n", __func__, num );
// find the point distances
node = hull->clipnodes + num;
plane = hull->planes + node->planenum;
if( world.version == QBSP2_VERSION )
{
children[0] = hull->clipnodes32[num].children[0];
children[1] = hull->clipnodes32[num].children[1];
plane = hull->planes + hull->clipnodes32[num].planenum;
}
else
{
children[0] = hull->clipnodes16[num].children[0];
children[1] = hull->clipnodes16[num].children[1];
plane = hull->planes + hull->clipnodes16[num].planenum;
}
t1 = PlaneDiff( p1, plane );
t2 = PlaneDiff( p2, plane );
if( t1 >= 0.0f && t2 >= 0.0f )
{
num = node->children[0];
num = children[0];
goto loc0;
}
if( t1 < 0.0f && t2 < 0.0f )
{
num = node->children[1];
num = children[1];
goto loc0;
}
@@ -257,14 +274,14 @@ loc0:
VectorLerp( p1, frac, p2, mid );
// move up to the node
if( !PM_RecursiveHullCheck( hull, node->children[side], p1f, midf, p1, mid, trace ))
if( !PM_RecursiveHullCheck( hull, children[side], p1f, midf, p1, mid, trace ))
return false;
// this recursion can not be optimized because mid would need to be duplicated on a stack
if( PM_HullPointContents( hull, node->children[side^1], mid ) != CONTENTS_SOLID )
if( PM_HullPointContents( hull, children[side^1], mid ) != CONTENTS_SOLID )
{
// go past the node
return PM_RecursiveHullCheck( hull, node->children[side^1], midf, p2f, mid, p2, trace );
return PM_RecursiveHullCheck( hull, children[side^1], midf, p2f, mid, p2, trace );
}
// never got out of the solid area

View File

@@ -109,44 +109,44 @@ void Sys_InitLog( void )
// fit to 80 columns for easier read on standard terminal
fputs( "================================================================================\n", s_ld.logfile );
fprintf( s_ld.logfile, "%s (%i, %s, %s, %s-%s)\n", s_ld.title, Q_buildnum(), Q_buildcommit(), Q_buildbranch(), Q_buildos(), Q_buildarch());
fprintf( s_ld.logfile, "%s (%i, %s, %s, %s-%s)\n", s_ld.title, Q_buildnum(), g_buildcommit, g_buildbranch, Q_buildos(), Q_buildarch());
fprintf( s_ld.logfile, "Game started at %s\n", Q_timestamp( TIME_FULL ));
fputs( "================================================================================\n", s_ld.logfile );
fflush( s_ld.logfile );
}
}
void Sys_CloseLog( void )
void Sys_CloseLog( const char *finalmsg )
{
char event_name[64];
// continue logged
switch( host.status )
{
case HOST_CRASHED:
Q_strncpy( event_name, "crashed", sizeof( event_name ));
break;
case HOST_ERR_FATAL:
Q_strncpy( event_name, "stopped with error", sizeof( event_name ));
break;
default:
if( !host.change_game ) Q_strncpy( event_name, "stopped", sizeof( event_name ));
else Q_strncpy( event_name, host.finalmsg, sizeof( event_name ));
break;
}
Sys_FlushStdout(); // flush to stdout to ensure all data was written
if( s_ld.logfile )
if( !s_ld.logfile )
return;
// continue logged
if( !finalmsg )
{
fputc( '\n', s_ld.logfile );
fputs( "================================================================================\n", s_ld.logfile );
fprintf( s_ld.logfile, "%s (%i, %s, %s, %s-%s)\n", s_ld.title, Q_buildnum(), Q_buildcommit(), Q_buildbranch(), Q_buildos(), Q_buildarch());
fprintf( s_ld.logfile, "Stopped with reason \"%s\" at %s\n", event_name, Q_timestamp( TIME_FULL ));
fputs( "================================================================================\n", s_ld.logfile );
fclose( s_ld.logfile );
s_ld.logfile = NULL;
switch( host.status )
{
case HOST_CRASHED:
finalmsg = "crashed";
break;
case HOST_ERR_FATAL:
finalmsg = "stopped with error";
break;
default:
finalmsg = "stopped";
break;
}
}
fputc( '\n', s_ld.logfile );
fputs( "================================================================================\n", s_ld.logfile );
fprintf( s_ld.logfile, "%s (%i, %s, %s, %s-%s)\n", s_ld.title, Q_buildnum(), g_buildcommit, g_buildbranch, Q_buildos(), Q_buildarch());
fprintf( s_ld.logfile, "Stopped with reason \"%s\" at %s\n", finalmsg, Q_timestamp( TIME_FULL ));
fputs( "================================================================================\n", s_ld.logfile );
fclose( s_ld.logfile );
s_ld.logfile = NULL;
}
#if XASH_COLORIZE_CONSOLE

View File

@@ -412,7 +412,7 @@ void Sys_Error( const char *error, ... )
Sys_WaitForQuit();
}
Sys_Quit();
Sys_Quit( "caught an error" );
}
#if XASH_EMSCRIPTEN
@@ -420,6 +420,12 @@ void Sys_Error( const char *error, ... )
_exit->_Exit->asm._exit->_exit
As we do not need atexit(), just throw hidden exception
*/
// Hey, you, making an Emscripten port!
// What if we're not supposed to use exit() on Emscripten and instead we should
// exit from the main() function? Would this fix this bug? Test this case, pls.
#error "Read the comment above"
#include <emscripten.h>
#define exit my_exit
void my_exit(int ret)
@@ -435,10 +441,14 @@ void my_exit(int ret)
Sys_Quit
================
*/
void Sys_Quit( void )
void Sys_Quit( const char *reason )
{
Host_Shutdown();
Host_ShutdownWithReason( reason );
#if XASH_ANDROID
Host_ExitInMain();
#else
exit( error_on_exit );
#endif
}
/*
@@ -529,7 +539,7 @@ but since engine will be unloaded during this call
it explicitly doesn't use internal allocation or string copy utils
==================
*/
qboolean Sys_NewInstance( const char *gamedir )
qboolean Sys_NewInstance( const char *gamedir, const char *finalmsg )
{
#if XASH_NSWITCH
char newargs[4096];
@@ -540,7 +550,7 @@ qboolean Sys_NewInstance( const char *gamedir )
// just restart the entire thing
printf( "envSetNextLoad exe: `%s`\n", exe );
printf( "envSetNextLoad argv:\n`%s`\n", newargs );
Host_Shutdown( );
Host_ShutdownWithReason( finalmsg );
envSetNextLoad( exe, newargs );
exit( 0 );
#else
@@ -578,7 +588,7 @@ qboolean Sys_NewInstance( const char *gamedir )
#if XASH_PSVITA
// under normal circumstances it's always going to be the same path
exe = strdup( "app0:/eboot.bin" );
Host_Shutdown( );
Host_ShutdownWithReason( finalmsg );
sceAppMgrLoadExec( exe, newargs, NULL );
#else
exelen = wai_getExecutablePath( NULL, 0, NULL );
@@ -586,7 +596,7 @@ qboolean Sys_NewInstance( const char *gamedir )
wai_getExecutablePath( exe, exelen, NULL );
exe[exelen] = 0;
Host_Shutdown();
Host_ShutdownWithReason( finalmsg );
execv( exe, newargs );
#endif

View File

@@ -55,13 +55,9 @@ void Sys_DebugBreak( void );
#define Sys_GetParmFromCmdLine( parm, out ) _Sys_GetParmFromCmdLine( parm, out, sizeof( out ))
qboolean _Sys_GetParmFromCmdLine( const char *parm, char *out, size_t size );
qboolean Sys_GetIntFromCmdLine( const char *parm, int *out );
void Sys_SendKeyEvents( void );
void Sys_Print( const char *pMsg );
void Sys_PrintLog( const char *pMsg );
void Sys_InitLog( void );
void Sys_CloseLog( void );
void Sys_Quit( void ) NORETURN;
qboolean Sys_NewInstance( const char *gamedir );
void Sys_Quit( const char *reason ) NORETURN;
qboolean Sys_NewInstance( const char *gamedir, const char *finalmsg );
void *Sys_GetNativeObject( const char *obj );
//
@@ -69,7 +65,7 @@ void *Sys_GetNativeObject( const char *obj );
//
char *Sys_Input( void );
void Sys_DestroyConsole( void );
void Sys_CloseLog( void );
void Sys_CloseLog( const char *finalmsg );
void Sys_InitLog( void );
void Sys_PrintLog( const char *pMsg );
int Sys_LogFileNo( void );

View File

@@ -16,11 +16,8 @@
#ifndef EDICT_H
#define EDICT_H
#ifdef SUPPORT_BSP2_FORMAT
#define MAX_ENT_LEAFS 24 // Orignally was 16
#else
#define MAX_ENT_LEAFS 48
#endif
#define MAX_ENT_LEAFS_32 24 // Orignally was 16
#define MAX_ENT_LEAFS_16 48
#include "progdefs.h"
@@ -33,11 +30,12 @@ struct edict_s
int headnode; // -1 to use normal leaf check
int num_leafs;
#ifdef SUPPORT_BSP2_FORMAT
int leafnums[MAX_ENT_LEAFS];
#else
short leafnums[MAX_ENT_LEAFS];
#endif
union
{
int leafnums32[MAX_ENT_LEAFS_32];
short leafnums16[MAX_ENT_LEAFS_16];
};
float freetime; // sv.time when the object was freed
void* pvPrivateData; // Alloced and freed by engine, used by DLLs

View File

@@ -34,6 +34,7 @@ long pathconf( const char *path, int name ) { return -1; }
// part of libunwind; required by any dynamic lib that uses C++ exceptions
extern void *_Unwind_Resume;
extern void *_Unwind_GetIPInfo;
// these are macros in our libc, so we need to wrap them
static int tolower_fn( int c ) { return tolower( c ); }
@@ -55,6 +56,7 @@ static const solder_export_t aux_exports[] =
SOLDER_EXPORT_SYMBOL( strchrnul ),
SOLDER_EXPORT_SYMBOL( stpcpy ),
SOLDER_EXPORT_SYMBOL( _Unwind_Resume ),
SOLDER_EXPORT_SYMBOL( _Unwind_GetIPInfo ),
};
const solder_export_t *__solder_aux_exports = aux_exports;

View File

@@ -253,14 +253,6 @@ static inline char *Platform_Input( void )
#endif
}
#if XASH_WIN32
void Platform_SendKeyEvents( void );
#else
static inline void Platform_SendKeyEvents( void )
{
}
#endif
/*
==============================================================================

View File

@@ -113,7 +113,7 @@ static void Sys_Crash( int signal, siginfo_t *si, void *context)
// safe actions first, stack and memory may be corrupted
len = Q_snprintf( message, sizeof( message ), "Ver: " XASH_ENGINE_NAME " " XASH_VERSION " (build %i-%s, %s-%s)\n",
Q_buildnum(), Q_buildcommit(), Q_buildos(), Q_buildarch() );
Q_buildnum(), g_buildcommit, Q_buildos(), Q_buildarch() );
#if !XASH_FREEBSD && !XASH_NETBSD && !XASH_OPENBSD
len += Q_snprintf( message + len, sizeof( message ) - len, "Crash: signal %d errno %d with code %d at %p %p\n", signal, si->si_errno, si->si_code, si->si_addr, si->si_ptr );
@@ -197,8 +197,7 @@ static void Sys_Crash( int signal, siginfo_t *si, void *context)
CL_Crashed();
host.status = HOST_CRASHED;
Sys_Quit();
Sys_Quit( "crashed" );
}
void Sys_SetupCrashHandler( void )

View File

@@ -148,7 +148,9 @@ void Posix_Daemonize( void )
static void Posix_SigtermCallback( int signal )
{
Sys_Quit();
string reason;
Con_Printf( reason, sizeof( reason ), "caught signal %d", signal );
Sys_Quit( reason );
}
void Posix_SetupSigtermHandling( void )

View File

@@ -396,8 +396,12 @@ static void SDLash_ActiveEvent( int gain )
}
#endif
host.status = HOST_NOFOCUS;
if( cls.key_dest == key_game )
{
Key_ClearStates();
IN_DeactivateMouse();
}
host.force_draw_version_time = host.realtime + 2.0;
VID_RestoreScreenResolution();
@@ -498,7 +502,7 @@ static void SDLash_EventHandler( SDL_Event *event )
break;
case SDL_QUIT:
Sys_Quit();
Sys_Quit( "caught SDL_QUIT" );
break;
#if SDL_VERSION_ATLEAST( 2, 0, 0 )
case SDL_MOUSEWHEEL:

View File

@@ -99,13 +99,43 @@ qboolean SNDDMA_Init( void )
{
SDL_AudioSpec desired, obtained;
int samplecount;
const char *driver = NULL;
// Modders often tend to use proprietary crappy solutions
// like FMOD to play music, sometimes even with versions outdated by a few decades!
//
// As these bullshit sound engines prefer to use DirectSound, we ask SDL2 to do
// the same. Why you might ask? If SDL2 uses another audio API, like WASAPI on
// more modern versions of Windows, it breaks the logic inside Windows, and the whole
// application could hang in WaitFor{Single,Multiple}Object function, either called by
// SDL2 if FMOD was shut down first, or deep in dsound.dll->fmod.dll if SDL2 audio
// was shut down first.
//
// I honestly don't know who is the real culprit here: FMOD, HL modders, Windows, SDL2
// or us.
//
// Also, fun note, GoldSrc seems doesn't use SDL2 for sound stuff at all, as nothing
// reference SDL audio functions there. It's probably has DirectSound backend, that's
// why modders never stumble upon this bug.
#if XASH_WIN32
driver = "directsound";
if( SDL_getenv( "SDL_AUDIODRIVER" ))
driver = NULL; // let SDL2 and user decide
SDL_SetHint( SDL_HINT_AUDIODRIVER, driver );
#endif // XASH_WIN32
// even if we don't have PA
// we still can safely set env variables
SDL_setenv( "PULSE_PROP_application.name", GI->title, 1 );
SDL_setenv( "PULSE_PROP_media.role", "game", 1 );
if( SDL_Init( SDL_INIT_AUDIO ))
// reinitialize SDL with our driver just in case
if( SDL_WasInit( SDL_INIT_AUDIO ))
SDL_QuitSubSystem( SDL_INIT_AUDIO );
if( SDL_InitSubSystem( SDL_INIT_AUDIO ))
{
Con_Reportf( S_ERROR "Audio: SDL: %s \n", SDL_GetError( ) );
return false;
@@ -212,7 +242,7 @@ void SNDDMA_Shutdown( void )
}
#if !XASH_EMSCRIPTEN
if( SDL_WasInit( SDL_INIT_AUDIO ) )
if( SDL_WasInit( SDL_INIT_AUDIO ))
SDL_QuitSubSystem( SDL_INIT_AUDIO );
#endif

View File

@@ -41,8 +41,54 @@ void Platform_MessageBox( const char *title, const char *message, qboolean paren
}
#endif // XASH_MESSAGEBOX == MSGBOX_SDL
static const char *SDLash_CategoryToString( int category )
{
switch( category )
{
case SDL_LOG_CATEGORY_APPLICATION: return "App";
case SDL_LOG_CATEGORY_ERROR: return "Error";
case SDL_LOG_CATEGORY_ASSERT: return "Assert";
case SDL_LOG_CATEGORY_SYSTEM: return "System";
case SDL_LOG_CATEGORY_AUDIO: return "Audio";
case SDL_LOG_CATEGORY_VIDEO: return "Video";
case SDL_LOG_CATEGORY_RENDER: return "Render";
case SDL_LOG_CATEGORY_INPUT: return "Input";
case SDL_LOG_CATEGORY_TEST: return "Test";
default: return "Unknown";
}
}
static void SDLCALL SDLash_LogOutputFunction( void *userdata, int category, SDL_LogPriority priority, const char *message )
{
switch( priority )
{
case SDL_LOG_PRIORITY_CRITICAL:
case SDL_LOG_PRIORITY_ERROR:
Con_Printf( S_ERROR S_BLUE "SDL" S_DEFAULT ": [%s] %s\n", SDLash_CategoryToString( category ), message );
break;
case SDL_LOG_PRIORITY_WARN:
Con_DPrintf( S_WARN S_BLUE "SDL" S_DEFAULT ": [%s] %s\n", SDLash_CategoryToString( category ), message );
break;
case SDL_LOG_PRIORITY_INFO:
Con_Reportf( S_NOTE S_BLUE "SDL" S_DEFAULT ": [%s] %s\n", SDLash_CategoryToString( category ), message );
break;
default:
Con_Reportf( S_BLUE "SDL" S_DEFAULT ": [%s] %s\n", SDLash_CategoryToString( category ), message );
break;
}
}
void SDLash_Init( void )
{
SDL_LogSetOutputFunction( SDLash_LogOutputFunction, NULL );
if( host_developer.value >= 2 )
SDL_LogSetAllPriority( SDL_LOG_PRIORITY_VERBOSE );
else if( host_developer.value >= 1 )
SDL_LogSetAllPriority( SDL_LOG_PRIORITY_WARN );
else
SDL_LogSetAllPriority( SDL_LOG_PRIORITY_ERROR );
#ifndef SDL_INIT_EVENTS
#define SDL_INIT_EVENTS 0
#endif

View File

@@ -588,7 +588,7 @@ void Wcon_DestroyConsole( void )
// last text message into console or log
Con_Reportf( "%s: Unloading xash.dll\n", __func__ );
Sys_CloseLog();
Sys_CloseLog( NULL );
if( !s_wcd.attached )
{

View File

@@ -138,7 +138,7 @@ static void Sys_StackTrace( PEXCEPTION_POINTERS pInfo )
#endif
len = Q_snprintf( message, sizeof( message ), "Ver: " XASH_ENGINE_NAME " " XASH_VERSION " (build %i-%s, %s-%s)\n",
Q_buildnum(), Q_buildcommit(), Q_buildos(), Q_buildarch() );
Q_buildnum(), g_buildcommit, Q_buildos(), Q_buildarch() );
len += Q_snprintf( message + len, 1024 - len, "Sys_Crash: address %p, code %p\n",
pInfo->ExceptionRecord->ExceptionAddress, (void*)pInfo->ExceptionRecord->ExceptionCode );
@@ -213,14 +213,14 @@ static void Sys_GetMinidumpFileName( const char *processName, char *mdmpFileName
struct tm *currentLocalTime = localtime( &currentUtcTime );
Q_snprintf( mdmpFileName, bufferSize, "%s_%s_crash_%d%.2d%.2d_%.2d%.2d%.2d.mdmp",
processName,
Q_buildcommit(),
currentLocalTime->tm_year + 1900,
currentLocalTime->tm_mon + 1,
currentLocalTime->tm_mday,
currentLocalTime->tm_hour,
currentLocalTime->tm_min,
currentLocalTime->tm_sec);
processName,
g_buildcommit,
currentLocalTime->tm_year + 1900,
currentLocalTime->tm_mon + 1,
currentLocalTime->tm_mday,
currentLocalTime->tm_hour,
currentLocalTime->tm_min,
currentLocalTime->tm_sec );
}
static qboolean Sys_WriteMinidump(PEXCEPTION_POINTERS exceptionInfo, MINIDUMP_TYPE minidumpType)
@@ -305,7 +305,7 @@ static long _stdcall Sys_Crash( PEXCEPTION_POINTERS pInfo )
if( host_developer.value <= 0 )
{
// no reason to call debugger in release build - just exit
Sys_Quit();
Sys_Quit( "crashed" );
return EXCEPTION_CONTINUE_EXECUTION;
}

View File

@@ -56,17 +56,3 @@ void Platform_MessageBox( const char *title, const char *message, qboolean paren
}
#endif // XASH_MESSAGEBOX == MSGBOX_WIN32
// I don't know why we need this or what this does
void Platform_SendKeyEvents( void )
{
MSG msg;
while( PeekMessage( &msg, NULL, 0, 0, PM_NOREMOVE ))
{
if( !GetMessage( &msg, NULL, 0, 0 ))
Sys_Quit ();
TranslateMessage( &msg );
DispatchMessage( &msg );
}
}

View File

@@ -80,6 +80,7 @@ GNU General Public License for more details.
#define MODEL_LIQUID BIT( 2 ) // model has only point hull
#define MODEL_TRANSPARENT BIT( 3 ) // have transparent surfaces
#define MODEL_COLORED_LIGHTING BIT( 4 ) // lightmaps stored as RGB
#define MODEL_WORLD BIT( 29 ) // it's a worldmodel
#define MODEL_CLIENT BIT( 30 ) // client sprite

View File

@@ -67,6 +67,8 @@ extern int SV_UPDATE_BACKUP;
#define MAX_PUSHED_ENTS 256
#define MAX_VIEWENTS 128
#define MAX_ENT_LEAFS( ext ) (( ext ) ? MAX_ENT_LEAFS_32 : MAX_ENT_LEAFS_16 )
#define FCL_RESEND_USERINFO BIT( 0 )
#define FCL_RESEND_MOVEVARS BIT( 1 )
#define FCL_SKIP_NET_MESSAGE BIT( 2 )
@@ -496,10 +498,24 @@ qboolean SV_ProcessUserAgent( netadr_t from, const char *useragent );
qboolean SV_InitGame( void );
void SV_ActivateServer( int runPhysics );
qboolean SV_SpawnServer( const char *server, const char *startspot, qboolean background );
model_t *SV_ModelHandle( int modelindex );
void SV_DeactivateServer( void );
void SV_FreeTestPacket( void );
/*
================
SV_ModelHandle
get model by handle
================
*/
static inline model_t *GAME_EXPORT SV_ModelHandle( int modelindex )
{
if( modelindex < 0 || modelindex >= MAX_MODELS )
return NULL;
return sv.models[modelindex];
}
//
// sv_phys.c
//
@@ -671,7 +687,6 @@ void SV_ClearGameState( void );
// sv_pmove.c
//
void SV_InitClientMove( void );
qboolean SV_PlayerIsFrozen( edict_t *pClient );
void SV_RunCmd( sv_client_t *cl, usercmd_t *ucmd, int random_seed );
//

View File

@@ -477,7 +477,7 @@ static void SV_ConnectClient( netadr_t from )
// reset stats
newcl->next_checkpingtime = -1.0;
newcl->packet_loss = 0.0f;
newcl->packet_loss = 0;
// if this was the first client on the server, or the last client
// the server can hold, send a heartbeat to the master.
@@ -1170,7 +1170,7 @@ SV_EstablishTimeBase
Finangles latency and the like.
===================
*/
static void SV_EstablishTimeBase( sv_client_t *cl, usercmd_t *cmds, int dropped, int numbackup, int numcmds )
static void SV_EstablishTimeBase( sv_client_t *cl, const usercmd_t *cmds, int dropped, int numbackup, int numcmds )
{
double runcmd_time = 0.0;
int i, cmdnum = dropped;
@@ -2226,7 +2226,7 @@ SV_SendBuildInfo_f
static qboolean SV_SendBuildInfo_f( sv_client_t *cl )
{
SV_ClientPrintf( cl, "Server running " XASH_ENGINE_NAME " " XASH_VERSION " (build %i-%s, %s-%s)\n",
Q_buildnum(), Q_buildcommit(), Q_buildos(), Q_buildarch() );
Q_buildnum(), g_buildcommit, Q_buildos(), Q_buildarch() );
return true;
}
@@ -3212,6 +3212,20 @@ void SV_ConnectionlessPacket( netadr_t from, sizebuf_t *msg )
}
}
static qboolean SV_PlayerIsFrozen( const edict_t *pClient )
{
if( sv_background_freeze.value && sv.background )
return true;
if( FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE ))
return false;
if( FBitSet( pClient->v.flags, FL_FROZEN ))
return true;
return false;
}
/*
==================
SV_ParseClientMove
@@ -3226,29 +3240,20 @@ each of the backup packets.
*/
static void SV_ParseClientMove( sv_client_t *cl, sizebuf_t *msg )
{
client_frame_t *frame;
int key, size, checksum1, checksum2;
int i, numbackup, totalcmds, numcmds;
usercmd_t nullcmd, *to, *from;
usercmd_t cmds[CMD_BACKUP];
float packet_loss;
edict_t *player;
model_t *model;
const usercmd_t nullcmd = { 0 }, *from = &nullcmd; // first cmd are starting from null-compressed usercmd_t
client_frame_t *frame = &cl->frames[cl->netchan.incoming_acknowledged & SV_UPDATE_MASK];
usercmd_t cmds[CMD_BACKUP] = { 0 }, *to;
edict_t *player = cl->edict;
model_t *model;
player = cl->edict;
int key = MSG_GetRealBytesRead( msg );
int checksum1 = MSG_ReadByte( msg );
int packet_loss = MSG_ReadByte( msg );
int numbackup = MSG_ReadByte( msg );
int numcmds = MSG_ReadByte( msg );
int totalcmds = numcmds + numbackup;
int i;
frame = &cl->frames[cl->netchan.incoming_acknowledged & SV_UPDATE_MASK];
memset( &nullcmd, 0, sizeof( usercmd_t ));
memset( cmds, 0, sizeof( cmds ));
key = MSG_GetRealBytesRead( msg );
checksum1 = MSG_ReadByte( msg );
packet_loss = MSG_ReadByte( msg );
numbackup = MSG_ReadByte( msg );
numcmds = MSG_ReadByte( msg );
totalcmds = numcmds + numbackup;
net_drop -= (numcmds - 1);
if( totalcmds < 0 || totalcmds >= CMD_MASK )
@@ -3258,8 +3263,6 @@ static void SV_ParseClientMove( sv_client_t *cl, sizebuf_t *msg )
return;
}
from = &nullcmd; // first cmd are starting from null-compressed usercmd_t
for( i = totalcmds - 1; i >= 0; i-- )
{
to = &cmds[i];
@@ -3270,14 +3273,17 @@ static void SV_ParseClientMove( sv_client_t *cl, sizebuf_t *msg )
if( cl->state != cs_spawned )
return;
// if the checksum fails, ignore the rest of the packet
size = MSG_GetRealBytesRead( msg ) - key - 1;
checksum2 = CRC32_BlockSequence( msg->pData + key + 1, size, cl->netchan.incoming_sequence );
if( checksum2 != checksum1 )
if( !Host_IsLocalClient( ))
{
Con_Reportf( S_ERROR "%s: failed command checksum for %s (%d != %d)\n", __func__, cl->name, checksum2, checksum1 );
return;
// if the checksum fails, ignore the rest of the packet
int size = MSG_GetRealBytesRead( msg ) - key - 1;
int checksum2 = CRC32_BlockSequence( msg->pData + key + 1, size, cl->netchan.incoming_sequence );
if( checksum2 != checksum1 )
{
Con_Reportf( S_ERROR "%s: failed command checksum for %s (%d != %d)\n", __func__, cl->name, checksum2, checksum1 );
return;
}
}
cl->packet_loss = packet_loss;

View File

@@ -492,14 +492,15 @@ SV_EmitPings
*/
static void SV_EmitPings( sizebuf_t *msg )
{
sv_client_t *cl;
int packet_loss;
int i, ping;
sv_client_t *cl;
int i;
MSG_BeginServerCmd( msg, svc_pings );
for( i = 0, cl = svs.clients; i < svs.maxclients; i++, cl++ )
{
int packet_loss, ping;
if( cl->state != cs_spawned )
continue;

View File

@@ -328,7 +328,7 @@ static qboolean SV_CheckClientVisiblity( sv_client_t *cl, const byte *mask )
else
VectorCopy( cl->edict->v.origin, vieworg );
leaf = Mod_PointInLeaf( vieworg, sv.worldmodel->nodes );
leaf = Mod_PointInLeaf( vieworg, sv.worldmodel->nodes, sv.worldmodel );
if( CHECKVISBIT( mask, leaf->cluster ))
return true; // visible from player view or camera view
@@ -342,7 +342,7 @@ static qboolean SV_CheckClientVisiblity( sv_client_t *cl, const byte *mask )
continue;
VectorAdd( view->v.origin, view->v.view_ofs, vieworg );
leaf = Mod_PointInLeaf( vieworg, sv.worldmodel->nodes );
leaf = Mod_PointInLeaf( vieworg, sv.worldmodel->nodes, sv.worldmodel );
if( CHECKVISBIT( mask, leaf->cluster ))
return true; // visible from portal camera view
@@ -1732,7 +1732,7 @@ static edict_t* GAME_EXPORT pfnFindClientInPVS( edict_t *pEdict )
VectorAdd( pEdict->v.origin, pEdict->v.view_ofs, view );
}
leaf = Mod_PointInLeaf( view, sv.worldmodel->nodes );
leaf = Mod_PointInLeaf( view, sv.worldmodel->nodes, sv.worldmodel );
if( CHECKVISBIT( clientpvs, leaf->cluster ))
return pClient; // client which currently in PVS
@@ -4291,6 +4291,35 @@ static byte *GAME_EXPORT pfnSetFatPAS( const float *org )
return fatphs;
}
/*
=============
Mod_HeadnodeVisible
=============
*/
static qboolean Mod_HeadnodeVisible( model_t *mod, mnode_t *node, const byte *visbits, int *lastleaf )
{
if( !node || node->contents == CONTENTS_SOLID )
return false;
if( node->contents < 0 )
{
if( !CHECKVISBIT( visbits, ((mleaf_t *)node)->cluster ))
return false;
if( lastleaf )
*lastleaf = ((mleaf_t *)node)->cluster;
return true;
}
if( Mod_HeadnodeVisible( mod, node_child( node, 0, mod ), visbits, lastleaf ))
return true;
if( Mod_HeadnodeVisible( mod, node_child( node, 1, mod ), visbits, lastleaf ))
return true;
return false;
}
/*
=============
pfnCheckVisibility
@@ -4300,6 +4329,7 @@ pfnCheckVisibility
static int GAME_EXPORT pfnCheckVisibility( const edict_t *ent, byte *pset )
{
int i, leafnum;
qboolean large_leafs = FBitSet( sv.worldmodel->flags, MODEL_QBSP2 );
if( !SV_IsValidEdict( ent ))
return 0;
@@ -4315,17 +4345,28 @@ static int GAME_EXPORT pfnCheckVisibility( const edict_t *ent, byte *pset )
// check individual leafs
for( i = 0; i < ent->num_leafs; i++ )
{
if( CHECKVISBIT( pset, ent->leafnums[i] ))
return 1; // visible passed by leaf
if( large_leafs )
{
if( CHECKVISBIT( pset, ent->leafnums32[i] ))
return 1; // visible passed by leaf
}
else
{
if( CHECKVISBIT( pset, ent->leafnums16[i] ))
return 1; // visible passed by leaf
}
}
return 0;
}
else
{
for( i = 0; i < MAX_ENT_LEAFS; i++ )
for( i = 0; i < MAX_ENT_LEAFS( large_leafs ); i++ )
{
leafnum = ent->leafnums[i];
if( large_leafs )
leafnum = ent->leafnums32[i];
else
leafnum = ent->leafnums16[i];
if( leafnum == -1 ) break;
if( CHECKVISBIT( pset, leafnum ))
@@ -4333,11 +4374,15 @@ static int GAME_EXPORT pfnCheckVisibility( const edict_t *ent, byte *pset )
}
// too many leafs for individual check, go by headnode
if( !Mod_HeadnodeVisible( &sv.worldmodel->nodes[ent->headnode], pset, &leafnum ))
if( !Mod_HeadnodeVisible( sv.worldmodel, &sv.worldmodel->nodes[ent->headnode], pset, &leafnum ))
return 0;
((edict_t *)ent)->leafnums[ent->num_leafs] = leafnum;
((edict_t *)ent)->num_leafs = (ent->num_leafs + 1) % MAX_ENT_LEAFS;
if( large_leafs )
((edict_t *)ent)->leafnums32[ent->num_leafs] = leafnum;
else
((edict_t *)ent)->leafnums16[ent->num_leafs] = leafnum;
((edict_t *)ent)->num_leafs = (ent->num_leafs + 1) % MAX_ENT_LEAFS( large_leafs );
return 2; // visible passed by headnode
}

View File

@@ -273,20 +273,6 @@ int GAME_EXPORT SV_GenericIndex( const char *filename )
return i;
}
/*
================
SV_ModelHandle
get model by handle
================
*/
model_t *GAME_EXPORT SV_ModelHandle( int modelindex )
{
if( modelindex < 0 || modelindex >= MAX_MODELS )
return NULL;
return sv.models[modelindex];
}
static resourcetype_t SV_DetermineResourceType( const char *filename )
{
if( !Q_strncmp( filename, DEFAULT_SOUNDPATH, sizeof( DEFAULT_SOUNDPATH ) - 1 ) && Sound_SupportedFileFormat( COM_FileExtension( filename )))

View File

@@ -970,7 +970,7 @@ void SV_Init( void )
MSG_Init( &net_message, "NetMessage", net_message_buffer, sizeof( net_message_buffer ));
Q_snprintf( versionString, sizeof( versionString ), XASH_ENGINE_NAME ": " XASH_VERSION "-%s(%s-%s),%i,%i",
Q_buildcommit(), Q_buildos(), Q_buildarch(), PROTOCOL_VERSION, Q_buildnum() );
g_buildcommit, Q_buildos(), Q_buildarch(), PROTOCOL_VERSION, Q_buildnum() );
Cvar_FullSet( "sv_version", versionString, FCVAR_READ_ONLY );

View File

@@ -23,19 +23,6 @@ GNU General Public License for more details.
static qboolean has_update = false;
static void SV_GetTrueOrigin( sv_client_t *cl, int edictnum, vec3_t origin );
qboolean SV_PlayerIsFrozen( edict_t *pClient )
{
if( sv_background_freeze.value && sv.background )
return true;
if( FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE ))
return false;
if( FBitSet( pClient->v.flags, FL_FROZEN ))
return true;
return false;
}
void SV_ClipPMoveToEntity( physent_t *pe, const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end, pmtrace_t *tr )
{
Assert( tr != NULL );

View File

@@ -1606,6 +1606,10 @@ static int LoadGameState( char const *level, qboolean changelevel )
pSaveData = LoadSaveData( level );
if( !pSaveData ) return 0; // couldn't load the file
// must set mapname before calling into DLL
Q_strncpy( sv.name, level, sizeof( sv.name ));
svgame.globals->mapname = MAKE_STRING( sv.name );
ParseSaveTables( pSaveData, &header, true );
EntityPatchRead( pSaveData, level );
@@ -1613,8 +1617,6 @@ static int LoadGameState( char const *level, qboolean changelevel )
sv.loadgame = sv.paused = true;
Cvar_SetValue( "skill", header.skillLevel );
Q_strncpy( sv.name, header.mapName, sizeof( sv.name ));
svgame.globals->mapname = MAKE_STRING( sv.name );
Cvar_Set( "sv_skyname", header.skyName );
// restore sky parms

View File

@@ -40,9 +40,8 @@ HULL BOXES
===============================================================================
*/
static hull_t box_hull;
static mclipnode_t box_clipnodes[6];
static mplane_t box_planes[6];
static hull_t box_hull;
static mplane_t box_planes[6];
/*
===================
@@ -54,23 +53,15 @@ can just be stored out and get a proper hull_t structure.
*/
static void SV_InitBoxHull( void )
{
int i, side;
int i;
box_hull.clipnodes = box_clipnodes;
box_hull.clipnodes16 = (mclipnode16_t *)box_clipnodes16;
box_hull.planes = box_planes;
box_hull.firstclipnode = 0;
box_hull.lastclipnode = 5;
for( i = 0; i < 6; i++ )
{
box_clipnodes[i].planenum = i;
side = i & 1;
box_clipnodes[i].children[side] = CONTENTS_EMPTY;
if( i != 5 ) box_clipnodes[i].children[side^1] = i + 1;
else box_clipnodes[i].children[side^1] = CONTENTS_SOLID;
box_planes[i].type = i>>1;
box_planes[i].normal[i>>1] = 1;
box_planes[i].signbits = 0;
@@ -167,6 +158,11 @@ static hull_t *SV_HullForBox( const vec3_t mins, const vec3_t maxs )
box_planes[4].dist = maxs[2];
box_planes[5].dist = mins[2];
if( world.version == QBSP2_VERSION )
box_hull.clipnodes32 = (mclipnode32_t *)box_clipnodes32;
else
box_hull.clipnodes16 = (mclipnode16_t *)box_clipnodes16;
return &box_hull;
}
@@ -594,7 +590,7 @@ SV_FindTouchedLeafs
===============
*/
static void SV_FindTouchedLeafs( edict_t *ent, mnode_t *node, int *headnode )
static void SV_FindTouchedLeafs( edict_t *ent, model_t *mod, mnode_t *node, int *headnode )
{
int sides;
mleaf_t *leaf;
@@ -605,16 +601,19 @@ static void SV_FindTouchedLeafs( edict_t *ent, mnode_t *node, int *headnode )
// add an efrag if the node is a leaf
if( node->contents < 0 )
{
if( ent->num_leafs > ( MAX_ENT_LEAFS - 1 ))
if( ent->num_leafs > MAX_ENT_LEAFS( FBitSet( mod->flags, MODEL_QBSP2 )))
{
// continue counting leafs,
// so we know how many it's overrun
ent->num_leafs = (MAX_ENT_LEAFS + 1);
ent->num_leafs = (MAX_ENT_LEAFS( FBitSet( mod->flags, MODEL_QBSP2 )) + 1);
}
else
{
leaf = (mleaf_t *)node;
ent->leafnums[ent->num_leafs] = leaf->cluster;
if( FBitSet( mod->flags, MODEL_QBSP2 ))
ent->leafnums32[ent->num_leafs] = leaf->cluster;
else
ent->leafnums16[ent->num_leafs] = leaf->cluster;
ent->num_leafs++;
}
return;
@@ -624,11 +623,13 @@ static void SV_FindTouchedLeafs( edict_t *ent, mnode_t *node, int *headnode )
sides = BOX_ON_PLANE_SIDE( ent->v.absmin, ent->v.absmax, node->plane );
if(( sides == 3 ) && ( *headnode == -1 ))
*headnode = node - sv.worldmodel->nodes;
*headnode = node - mod->nodes;
// recurse down the contacted sides
if( sides & 1 ) SV_FindTouchedLeafs( ent, node->children[0], headnode );
if( sides & 2 ) SV_FindTouchedLeafs( ent, node->children[1], headnode );
if( sides & 1 )
SV_FindTouchedLeafs( ent, mod, node_child( node, 0, mod ), headnode );
if( sides & 2 )
SV_FindTouchedLeafs( ent, mod, node_child( node, 1, mod ), headnode );
}
/*
@@ -650,7 +651,7 @@ void GAME_EXPORT SV_LinkEdict( edict_t *ent, qboolean touch_triggers )
if( ent->v.movetype == MOVETYPE_FOLLOW && SV_IsValidEdict( ent->v.aiment ))
{
memcpy( ent->leafnums, ent->v.aiment->leafnums, sizeof( ent->leafnums ));
memcpy( ent->leafnums32, ent->v.aiment->leafnums32, sizeof( ent->leafnums32 ));
ent->num_leafs = ent->v.aiment->num_leafs;
ent->headnode = ent->v.aiment->headnode;
}
@@ -662,11 +663,11 @@ void GAME_EXPORT SV_LinkEdict( edict_t *ent, qboolean touch_triggers )
headnode = -1;
if( ent->v.modelindex )
SV_FindTouchedLeafs( ent, sv.worldmodel->nodes, &headnode );
SV_FindTouchedLeafs( ent, sv.worldmodel, sv.worldmodel->nodes, &headnode );
if( ent->num_leafs > MAX_ENT_LEAFS )
if( ent->num_leafs > MAX_ENT_LEAFS( FBitSet( sv.worldmodel->flags, MODEL_QBSP2 )))
{
memset( ent->leafnums, -1, sizeof( ent->leafnums ));
memset( ent->leafnums32, -1, sizeof( ent->leafnums32 ));
ent->num_leafs = 0; // so we use headnode instead
ent->headnode = headnode;
}
@@ -1518,6 +1519,8 @@ static qboolean SV_RecursiveLightPoint( model_t *model, mnode_t *node, const vec
float front, back, frac;
int i, side;
vec3_t mid;
mnode_t *children[2];
int numsurfaces, firstsurface;
// didn't hit anything
if( !node || node->contents < 0 )
@@ -1527,25 +1530,29 @@ static qboolean SV_RecursiveLightPoint( model_t *model, mnode_t *node, const vec
front = PlaneDiff( start, node->plane );
back = PlaneDiff( end, node->plane );
node_children( children, node, model );
side = front < 0.0f;
if(( back < 0.0f ) == side )
return SV_RecursiveLightPoint( model, node->children[side], start, end, point_color );
return SV_RecursiveLightPoint( model, children[side], start, end, point_color );
frac = front / ( front - back );
VectorLerp( start, frac, end, mid );
// co down front side
if( SV_RecursiveLightPoint( model, node->children[side], start, mid, point_color ))
if( SV_RecursiveLightPoint( model, children[side], start, mid, point_color ))
return true; // hit something
if(( back < 0.0f ) == side )
return false; // didn't hit anything
// check for impact on this node
for( i = 0; i < node->numsurfaces; i++ )
numsurfaces = node_numsurfaces( node, model );
firstsurface = node_firstsurface( node, model );
for( i = 0; i < numsurfaces; i++ )
{
const msurface_t *surf = &model->surfaces[node->firstsurface + i];
const msurface_t *surf = &model->surfaces[firstsurface + i];
const mextrasurf_t *info = surf->info;
int smax, tmax, map, size;
int sample_size;
@@ -1596,7 +1603,7 @@ static qboolean SV_RecursiveLightPoint( model_t *model, mnode_t *node, const vec
}
// go down back side
return SV_RecursiveLightPoint( model, node->children[!side], mid, end, point_color );
return SV_RecursiveLightPoint( model, children[!side], mid, end, point_color );
}
/*

View File

@@ -170,9 +170,9 @@ typedef struct vguiapi_s
void (*SetupDrawingImage)( int *pColor );
void (*BindTexture)( int id );
void (*EnableTexture)( qboolean enable );
void (*CreateTexture)( int id, int width, int height );
void (*Reserved0)( int id, int width, int height );
void (*UploadTexture)( int id, const char *buffer, int width, int height );
void (*UploadTextureBlock)( int id, int drawX, int drawY, const byte *rgba, int blockWidth, int blockHeight );
void (*Reserved1)( int id, int drawX, int drawY, const byte *rgba, int blockWidth, int blockHeight );
void (*DrawQuad)( const vpoint_t *ul, const vpoint_t *lr );
void (*GetTextureSizes)( int *width, int *height );
int (*GenerateTexture)( void );

View File

@@ -57,6 +57,12 @@ static qboolean Platform_GetDirectoryCaseSensitivity( const char *dir )
{
#if XASH_WIN32 || XASH_PSVITA || XASH_NSWITCH
return false;
#elif XASH_ANDROID
// on Android, doing code below causes crash in MediaProviderGoogle.apk!libfuse_jni.so
// which in turn makes vold (Android's Volume Daemon) to umount /storage/emulated/0
// and because you can't unmount a filesystem when there is file descriptors open
// it has no other choice but to terminate and then kill our program
return true;
#elif XASH_LINUX && defined( FS_IOC_GETFLAGS )
int flags = 0;
int fd;
@@ -66,7 +72,10 @@ static qboolean Platform_GetDirectoryCaseSensitivity( const char *dir )
return true;
if( ioctl( fd, FS_IOC_GETFLAGS, &flags ) < 0 )
{
close( fd );
return true;
}
close( fd );
@@ -134,7 +143,7 @@ static void FS_PopulateDirEntries( dir_t *dir, const char *path )
}
stringlistinit( &list );
listdirectory( &list, path );
listdirectory( &list, path, false );
if( !list.numstrings )
{
dir->numentries = DIRENTRY_EMPTY_DIRECTORY;
@@ -225,7 +234,7 @@ static int FS_MaybeUpdateDirEntries( dir_t *dir, const char *path, const char *e
int ret;
stringlistinit( &list );
listdirectory( &list, path );
listdirectory( &list, path, false );
if( list.numstrings == 0 ) // empty directory
{
@@ -421,7 +430,7 @@ static void FS_Search_DIR( searchpath_t *search, stringlist_t *list, const char
}
stringlistinit( &dirlist );
listdirectory( &dirlist, netpath );
listdirectory( &dirlist, netpath, false );
Q_strncpy( temp, basepath, sizeof( temp ));

View File

@@ -225,18 +225,13 @@ static void listlowercase( stringlist_t *list )
}
#endif
void listdirectory( stringlist_t *list, const char *path )
void listdirectory( stringlist_t *list, const char *path, qboolean dirs_only )
{
#if XASH_WIN32
char pattern[4096];
struct _finddata_t n_file;
intptr_t hFile;
#else
DIR *dir;
struct dirent *entry;
#endif
struct _finddata_t n_file;
intptr_t hFile;
#if XASH_WIN32
Q_snprintf( pattern, sizeof( pattern ), "%s/*", path );
// ask for the directory listing handle
@@ -247,15 +242,33 @@ void listdirectory( stringlist_t *list, const char *path )
stringlistappend( list, n_file.name );
// iterate through the directory
while( _findnext( hFile, &n_file ) == 0 )
{
if( dirs_only && !FBitSet( n_file.attrib, _A_SUBDIR ))
continue;
stringlistappend( list, n_file.name );
}
_findclose( hFile );
#else
if( !( dir = opendir( path ) ) )
DIR *dir;
struct dirent *entry;
dir = opendir( path );
if( !dir )
return;
// iterate through the directory
while( ( entry = readdir( dir ) ))
while(( entry = readdir( dir )))
{
#if HAVE_DIRENT_D_TYPE
if( dirs_only && entry->d_type != DT_DIR && entry->d_type != DT_UNKNOWN )
continue;
#endif
stringlistappend( list, entry->d_name );
}
closedir( dir );
#endif
@@ -396,7 +409,7 @@ void FS_AddGameDirectory( const char *dir, uint flags )
int i;
stringlistinit( &list );
listdirectory( &list, dir );
listdirectory( &list, dir, false );
stringlistsort( &list );
for( archive = g_archives; archive->ext; archive++ )
@@ -508,14 +521,15 @@ FS_WriteGameInfo
assume GameInfo is valid
================
*/
static void FS_WriteGameInfo( const char *filepath, gameinfo_t *GameInfo )
static qboolean FS_WriteGameInfo( const char *filepath, gameinfo_t *GameInfo )
{
file_t *f = FS_Open( filepath, "w", false ); // we in binary-mode
int i, write_ambients = false;
if( !f ) Sys_Error( "%s: can't write %s\n", __func__, filepath ); // may be disk-space is out?
if( !f )
return false;
FS_Printf( f, "// generated by " XASH_ENGINE_NAME " " XASH_VERSION "-%s (%s-%s)\n\n\n", Q_buildcommit(), Q_buildos(), Q_buildarch() );
FS_Printf( f, "// generated by " XASH_ENGINE_NAME " " XASH_VERSION "-%s (%s-%s)\n\n\n", g_buildcommit, Q_buildos(), Q_buildarch() );
if( COM_CheckStringEmpty( GameInfo->basedir ) )
FS_Printf( f, "basedir\t\t\"%s\"\n", GameInfo->basedir );
@@ -637,25 +651,50 @@ static void FS_WriteGameInfo( const char *filepath, gameinfo_t *GameInfo )
FS_Printf( f, "demomap\t\t\"%s\"\n", GameInfo->demomap );
FS_Close( f ); // all done
return true;
}
static void FS_InitGameInfo( gameinfo_t *GameInfo, const char *gamedir )
static void FS_MakeGameInfo( void )
{
if( FS_WriteGameInfo( "gameinfo.txt", FI.GameInfo ))
Con_Printf( "Successfully generated %s/gameinfo.txt\n", FI.GameInfo->gamefolder );
else
Con_Printf( S_ERROR "Can't open %s/gameinfo.txt for write\n", FI.GameInfo->gamefolder );
}
static void FS_InitGameInfo( gameinfo_t *GameInfo, const char *gamedir, qboolean quake, time_t mtime )
{
memset( GameInfo, 0, sizeof( *GameInfo ));
// filesystem info
Q_strncpy( GameInfo->title, "New Game", sizeof( GameInfo->title ));
GameInfo->mtime = mtime;
Q_strncpy( GameInfo->gamefolder, gamedir, sizeof( GameInfo->gamefolder ));
Q_strncpy( GameInfo->basedir, fs_basedir, sizeof( GameInfo->basedir ));
Q_strncpy( GameInfo->sp_entity, "info_player_start", sizeof( GameInfo->sp_entity ));
Q_strncpy( GameInfo->mp_entity, "info_player_deathmatch", sizeof( GameInfo->mp_entity ));
Q_strncpy( GameInfo->startmap, "newmap", sizeof( GameInfo->startmap ));
Q_strncpy( GameInfo->dll_path, "cl_dlls", sizeof( GameInfo->dll_path ));
Q_strncpy( GameInfo->game_dll, "dlls/hl.dll", sizeof( GameInfo->game_dll ));
Q_strncpy( GameInfo->game_dll_linux, "dlls/hl.so", sizeof( GameInfo->game_dll_linux ));
Q_strncpy( GameInfo->game_dll_osx, "dlls/hl.dylib", sizeof( GameInfo->game_dll_osx ));
Q_strncpy( GameInfo->iconpath, "game.ico", sizeof( GameInfo->iconpath ));
if( quake )
{
Q_strncpy( GameInfo->basedir, "id1", sizeof( GameInfo->basedir ));
Q_strncpy( GameInfo->title, gamedir, sizeof( GameInfo->title ));
Q_strncpy( GameInfo->startmap, "start", sizeof( GameInfo->startmap ));
Q_strncpy( GameInfo->dll_path, "bin", sizeof( GameInfo->dll_path ));
Q_strncpy( GameInfo->game_dll, "bin/progs.dll", sizeof( GameInfo->game_dll ));
Q_strncpy( GameInfo->game_dll_linux, "bin/progs.so", sizeof( GameInfo->game_dll_linux ));
Q_strncpy( GameInfo->game_dll_osx, "bin/progs.dylib", sizeof( GameInfo->game_dll_osx ));
}
else
{
Q_strncpy( GameInfo->basedir, fs_basedir, sizeof( GameInfo->basedir ));
Q_strncpy( GameInfo->title, "New Game", sizeof( GameInfo->title ));
Q_strncpy( GameInfo->startmap, "newmap", sizeof( GameInfo->startmap ));
Q_strncpy( GameInfo->dll_path, "cl_dlls", sizeof( GameInfo->dll_path ));
Q_strncpy( GameInfo->game_dll, "dlls/hl.dll", sizeof( GameInfo->game_dll ));
Q_strncpy( GameInfo->game_dll_linux, "dlls/hl.so", sizeof( GameInfo->game_dll_linux ));
Q_strncpy( GameInfo->game_dll_osx, "dlls/hl.dylib", sizeof( GameInfo->game_dll_osx ));
}
GameInfo->max_edicts = DEFAULT_MAX_EDICTS; // default value if not specified
GameInfo->max_tents = 500;
GameInfo->max_beams = 128;
@@ -940,36 +979,19 @@ static void FS_ParseGenericGameInfo( gameinfo_t *GameInfo, const char *buf, cons
}
}
/*
================
FS_CreateDefaultGameInfo
================
*/
static void FS_CreateDefaultGameInfo( const char *filename )
{
gameinfo_t defGI;
FS_InitGameInfo( &defGI, fs_basedir );
// make simple gameinfo.txt
FS_WriteGameInfo( filename, &defGI );
}
/*
================
FS_ParseLiblistGam
================
*/
static qboolean FS_ParseLiblistGam( const char *filename, const char *gamedir, gameinfo_t *GameInfo )
static qboolean FS_ParseLiblistGam( const char *filename, const char *gamedir, gameinfo_t *GameInfo, time_t mtime )
{
char *afile;
char *afile = FS_LoadDirectFile( filename, NULL );
if( !GameInfo ) return false;
afile = (char *)FS_LoadDirectFile( filename, NULL );
if( !afile ) return false;
FS_InitGameInfo( GameInfo, gamedir );
if( !afile )
return false;
FS_InitGameInfo( GameInfo, gamedir, false, mtime );
FS_ParseGenericGameInfo( GameInfo, afile, false );
Mem_Free( afile );
@@ -982,18 +1004,15 @@ static qboolean FS_ParseLiblistGam( const char *filename, const char *gamedir, g
FS_ConvertGameInfo
================
*/
static qboolean FS_ConvertGameInfo( const char *gamedir, const char *gameinfo_path, const char *liblist_path )
static qboolean FS_ConvertGameInfo( const char *gamedir, const char *gameinfo_path, const char *liblist_path, gameinfo_t *gi, time_t liblist_mtime )
{
gameinfo_t GameInfo;
memset( gi, 0, sizeof( *gi ));
memset( &GameInfo, 0, sizeof( GameInfo ));
if( FS_ParseLiblistGam( liblist_path, gamedir, &GameInfo ))
// liblist.gam to gameinfo.txt conversion is deprecated, only support for RwDir!
if( FS_ParseLiblistGam( liblist_path, gamedir, gi, liblist_mtime ))
{
Con_DPrintf( "Convert %s to %s\n", liblist_path, gameinfo_path );
FS_WriteGameInfo( gameinfo_path, &GameInfo );
return true;
return FS_WriteGameInfo( gameinfo_path, gi );
}
return false;
@@ -1004,16 +1023,14 @@ static qboolean FS_ConvertGameInfo( const char *gamedir, const char *gameinfo_pa
FS_ReadGameInfo
================
*/
static qboolean FS_ReadGameInfo( const char *filepath, const char *gamedir, gameinfo_t *GameInfo )
static qboolean FS_ReadGameInfo( const char *filename, const char *gamedir, gameinfo_t *GameInfo, time_t mtime )
{
char *afile;
char *afile = FS_LoadDirectFile( filename, NULL );
afile = (char *)FS_LoadFile( filepath, NULL, false );
if( !afile )
return false;
FS_InitGameInfo( GameInfo, gamedir );
FS_InitGameInfo( GameInfo, gamedir, false, mtime );
FS_ParseGenericGameInfo( GameInfo, afile, true );
Mem_Free( afile );
@@ -1029,20 +1046,32 @@ Checks if game directory resembles Quake Engine game directory
(some of checks may as well work with Xash gamedirs, it's not a bug)
================
*/
static qboolean FS_CheckForQuakeGameDir( const char *gamedir, qboolean direct )
static qboolean FS_CheckForQuakeGameDir( const char *gamedir )
{
// if directory contain config.cfg or progs.dat it's 100% gamedir
// quake mods probably always archived but can missed config.cfg before first running
const char *files[] = { "config.cfg", "progs.dat", "pak0.pak" };
// if directory contain quake.rc or progs.dat it's 100% quake gamedir
// quake mods probably always archived, so check pak0.pak too
const char *files[] = { "progs.dat", "quake.rc" };
char buf[MAX_SYSPATH];
int i;
// try to read pak0.pak first, most quake mods are archived
if( Q_snprintf( buf, sizeof( buf ), "%s/pak0.pak", gamedir ) > 0 )
{
if( FS_SysFileExists( buf ))
{
if( FS_CheckForQuakePak( buf, files, sizeof( files ) / sizeof( files[0] )))
return true;
}
}
// search it in the filesystem
for( i = 0; i < sizeof( files ) / sizeof( files[0] ); i++ )
{
char buf[MAX_VA_STRING];
Q_snprintf( buf, sizeof( buf ), "%s/%s", gamedir, files[i] );
if( direct ? FS_SysFileExists( buf ) : FS_FileExists( buf, false ))
return true;
if( Q_snprintf( buf, sizeof( buf ), "%s/%s", gamedir, files[i] ) > 0 )
{
if( FS_SysFileExists( buf ))
return true;
}
}
return false;
@@ -1055,7 +1084,7 @@ FS_CheckForXashGameDir
Checks if game directory resembles Xash3D game directory
===============
*/
static qboolean FS_CheckForXashGameDir( const char *gamedir, qboolean direct )
static qboolean FS_CheckForXashGameDir( const char *gamedir )
{
// if directory contain gameinfo.txt or liblist.gam it's 100% gamedir
const char *files[] = { "gameinfo.txt", "liblist.gam" };
@@ -1063,15 +1092,16 @@ static qboolean FS_CheckForXashGameDir( const char *gamedir, qboolean direct )
for( i = 0; i < sizeof( files ) / sizeof( files[0] ); i++ )
{
char buf[MAX_SYSPATH];
char buf[MAX_SYSPATH];
Q_snprintf( buf, sizeof( buf ), "%s/%s", gamedir, files[i] );
if( direct ? FS_SysFileExists( buf ) : FS_FileExists( buf, false ))
return true;
if( Q_snprintf( buf, sizeof( buf ), "%s/%s", gamedir, files[i] ) > 0 )
{
if( FS_SysFileExists( buf ))
return true;
}
}
return false;
}
/*
@@ -1079,86 +1109,74 @@ static qboolean FS_CheckForXashGameDir( const char *gamedir, qboolean direct )
FS_ParseGameInfo
================
*/
static qboolean FS_ParseGameInfo( const char *gamedir, gameinfo_t *GameInfo )
static qboolean FS_ParseGameInfo( const char *gamedir, gameinfo_t *GameInfo, qboolean rodir )
{
string liblist_path, gameinfo_path;
string default_gameinfo_path;
qboolean haveUpdate = false;
char liblist_path[MAX_SYSPATH];
char gameinfo_path[MAX_SYSPATH];
char gamedir_path[MAX_SYSPATH];
time_t liblist_mtime = -1;
time_t gameinfo_mtime = -1;
Q_snprintf( default_gameinfo_path, sizeof( default_gameinfo_path ), "%s/gameinfo.txt", fs_basedir );
Q_snprintf( gameinfo_path, sizeof( gameinfo_path ), "%s/gameinfo.txt", gamedir );
Q_snprintf( liblist_path, sizeof( liblist_path ), "%s/liblist.gam", gamedir );
if( rodir )
Q_snprintf( gamedir_path, sizeof( gamedir_path ), "%s/%s", fs_rodir, gamedir );
else
Q_snprintf( gamedir_path, sizeof( gamedir_path ), "%s/%s", fs_rootdir, gamedir );
// here goes some RoDir magic...
if( COM_CheckStringEmpty( fs_rodir ))
if( !FS_CheckForXashGameDir( gamedir_path ))
{
string gameinfo_ro, liblist_ro;
fs_offset_t roLibListTime, roGameInfoTime, rwGameInfoTime;
FS_AllowDirectPaths( true );
Q_snprintf( gameinfo_ro, sizeof( gameinfo_ro ), "%s/%s/gameinfo.txt", fs_rodir, gamedir );
Q_snprintf( liblist_ro, sizeof( liblist_ro ), "%s/%s/liblist.gam", fs_rodir, gamedir );
roLibListTime = FS_SysFileTime( liblist_ro );
roGameInfoTime = FS_SysFileTime( gameinfo_ro );
rwGameInfoTime = FS_SysFileTime( gameinfo_path );
// if rodir's liblist.gam newer than rwdir's gameinfo.txt, then convert it
if( roLibListTime > rwGameInfoTime )
// check if we need to generate gameinfo for Quake
if( FS_CheckForQuakeGameDir( gamedir_path ))
{
haveUpdate = FS_ConvertGameInfo( gamedir, gameinfo_path, liblist_ro );
}
// if rodir's gameinfo.txt newer than rwdir's gameinfo.txt, just copy the file
else if( roGameInfoTime > rwGameInfoTime )
{
file_t *ro, *rw;
fs_offset_t ro_size;
// read & write as binary to copy the exact file
ro = FS_SysOpen( gameinfo_ro, "rb" );
rw = FS_SysOpen( gameinfo_path, "wb" );
FS_Seek( ro, 0, SEEK_END );
ro_size = FS_Tell( ro );
FS_Seek( ro, 0, SEEK_SET );
FS_FileCopy( rw, ro, ro_size );
FS_Close( rw );
FS_Close( ro );
haveUpdate = true;
// just generate stub gameinfo in memory
FS_InitGameInfo( GameInfo, gamedir, true, -1 );
GameInfo->rodir = rodir;
return true;
}
FS_AllowDirectPaths( false );
// don't add empty or addon directories
return false;
}
// do not update gameinfo.txt, if it was just copied from rodir's
// if user change liblist.gam update the gameinfo.txt
if( !haveUpdate && FS_FileTime( liblist_path, false ) > FS_FileTime( gameinfo_path, false ))
FS_ConvertGameInfo( gamedir, gameinfo_path, liblist_path );
Q_snprintf( gameinfo_path, sizeof( gameinfo_path ), "%s/gameinfo.txt", gamedir_path );
Q_snprintf( liblist_path, sizeof( liblist_path ), "%s/liblist.gam", gamedir_path );
// force to create gameinfo for specified game if missing
if(( FS_CheckForQuakeGameDir( gamedir, false ) || !Q_stricmp( fs_gamedir, gamedir )) && !FS_FileExists( gameinfo_path, false ))
liblist_mtime = FS_SysFileTime( liblist_path );
gameinfo_mtime = FS_SysFileTime( gameinfo_path );
// in this function we never write new files for RoDir compatibility
// since RoDir is only FWGS feature, do gameinfo.txt conversion only
// for RwDir for those who worked with original Xash3D
if( !rodir )
{
gameinfo_t tmpGameInfo;
memset( &tmpGameInfo, 0, sizeof( tmpGameInfo ));
if( FS_ReadGameInfo( default_gameinfo_path, gamedir, &tmpGameInfo ))
// !!!only if we have both liblist.gam and gameinfo.txt try to convert liblist.gam to gameinfo.txt if it's newer!!!
if( liblist_mtime >= 0 && gameinfo_mtime >= 0 && liblist_mtime > gameinfo_mtime )
{
// now we have copy of game info from basedir but needs to change gamedir
Con_DPrintf( "Convert %s to %s\n", default_gameinfo_path, gameinfo_path );
Q_strncpy( tmpGameInfo.gamefolder, gamedir, sizeof( tmpGameInfo.gamefolder ));
FS_WriteGameInfo( gameinfo_path, &tmpGameInfo );
if( FS_ConvertGameInfo( gamedir, gameinfo_path, liblist_path, GameInfo, liblist_mtime ))
return true;
}
else FS_CreateDefaultGameInfo( gameinfo_path );
}
if( !GameInfo || !FS_FileExists( gameinfo_path, false ))
return false; // no dest
// can we parse gameinfo.txt?
if( gameinfo_mtime >= 0 )
{
if( FS_ReadGameInfo( gameinfo_path, gamedir, GameInfo, gameinfo_mtime ))
{
GameInfo->rodir = rodir;
return true;
}
}
return FS_ReadGameInfo( gameinfo_path, gamedir, GameInfo );
// can we parse liblist.gam?
if( liblist_mtime >= 0 )
{
if( FS_ParseLiblistGam( liblist_path, gamedir, GameInfo, liblist_mtime ))
{
GameInfo->rodir = rodir;
return true;
}
}
return false;
}
/*
@@ -1197,7 +1215,7 @@ void FS_AddGameHierarchy( const char *dir, uint flags )
}
}
if( COM_CheckStringEmpty( fs_rodir ) )
if( COM_CheckStringEmpty( fs_rodir ))
{
// append new flags to rodir, except FS_GAMEDIR_PATH and FS_CUSTOM_PATH
uint new_flags = FS_NOWRITE_PATH | (flags & (~FS_GAMEDIR_PATH|FS_CUSTOM_PATH));
@@ -1266,10 +1284,12 @@ void FS_LoadGameInfo( const char *rootfolder )
// lock uplevel of gamedir for read\write
FS_AllowDirectPaths( false );
if( rootfolder ) Q_strncpy( fs_gamedir, rootfolder, sizeof( fs_gamedir ));
if( rootfolder )
Q_strncpy( fs_gamedir, rootfolder, sizeof( fs_gamedir ));
Con_Reportf( "%s( %s )\n", __func__, fs_gamedir );
// clear any old pathes
// clear any old paths
FS_ClearSearchPath();
// validate gamedir
@@ -1284,6 +1304,14 @@ void FS_LoadGameInfo( const char *rootfolder )
FI.GameInfo = FI.games[i];
if( FI.GameInfo->rodir )
{
// ensure we have directory in rwdir
char buf[MAX_SYSPATH + MAX_QPATH + 2]; // and have plenty of space
Q_snprintf( buf, sizeof( buf ), "%s/%s/", fs_rootdir, FI.GameInfo->gamefolder );
FS_CreatePath( buf );
}
FS_Rescan(); // create new filesystem
}
@@ -1344,7 +1372,7 @@ static qboolean FS_FindLibrary( const char *dllname, qboolean directpath, fs_dll
{
string fixedname;
searchpath_t *search;
int index, start = 0, i, len;
int index, start = 0, len;
// check for bad exports
if( !COM_CheckString( dllname ))
@@ -1479,6 +1507,33 @@ static void *Sys_GetNativeObjectStub( const char *object )
return NULL;
}
static void FS_ValidateDirectories( const char *path, qboolean *has_base_dir, qboolean *has_game_dir )
{
stringlist_t dirs;
int i;
stringlistinit( &dirs );
listdirectory( &dirs, path, true );
stringlistsort( &dirs );
for( i = 0; i < dirs.numstrings; i++ )
{
if( !FS_SysFolderExists( dirs.strings[i] ))
continue;
if( !Q_stricmp( fs_basedir, dirs.strings[i] ))
*has_base_dir = true;
if( !Q_stricmp( fs_gamedir, dirs.strings[i] ))
*has_game_dir = true;
if( *has_base_dir && *has_game_dir )
break;
}
stringlistfreecontents( &dirs );
}
/*
================
FS_Init
@@ -1486,11 +1541,9 @@ FS_Init
*/
qboolean FS_InitStdio( qboolean unused_set_to_true, const char *rootdir, const char *basedir, const char *gamedir, const char *rodir )
{
stringlist_t dirs;
qboolean hasBaseDir = false;
qboolean hasGameDir = false;
int i;
char buf[MAX_VA_STRING];
stringlist_t dirs;
int i, rodir_num_games;
char buf[MAX_SYSPATH];
FS_InitMemory();
@@ -1503,63 +1556,40 @@ qboolean FS_InitStdio( qboolean unused_set_to_true, const char *rootdir, const c
Q_strncpy( fs_basedir, basedir, sizeof( fs_basedir ));
Q_strncpy( fs_rodir, rodir, sizeof( fs_rodir ));
// add readonly directories first
if( COM_CheckStringEmpty( fs_rodir ))
// validate user input
if( COM_CheckStringEmpty( fs_rodir ) && !Q_stricmp( fs_rodir, fs_rootdir ))
{
if( !Q_stricmp( fs_rodir, fs_rootdir ))
Sys_Error( "RoDir and default rootdir can't point to same directory!" );
return false;
}
// look for game directories in RwDir first
// this whole check only required to fallback to base directory
// (i.e. default game directory hardcoded in the executable file)
{
qboolean has_base_dir = false;
qboolean has_game_dir = false;
FS_ValidateDirectories( "./", &has_base_dir, &has_game_dir );
if( !has_game_dir )
{
Sys_Error( "RoDir and default rootdir can't point to same directory!" );
return false;
}
// look for game directories in RoDir now
if( COM_CheckStringEmpty( fs_rodir ))
FS_ValidateDirectories( fs_rodir, &has_base_dir, &has_game_dir );
stringlistinit( &dirs );
listdirectory( &dirs, fs_rodir );
stringlistsort( &dirs );
for( i = 0; i < dirs.numstrings; i++ )
{
char roPath[MAX_SYSPATH];
Q_snprintf( roPath, sizeof( roPath ), "%s/%s/", fs_rodir, dirs.strings[i] );
// check if it's a directory
if( !FS_SysFolderExists( roPath ))
continue;
// check if it's gamedir
if( FS_CheckForXashGameDir( roPath, true ) || FS_CheckForQuakeGameDir( roPath, true ))
if( !has_game_dir )
{
char rwPath[MAX_SYSPATH];
Con_Printf( S_ERROR "game directory \"%s\" not exist\n", fs_gamedir );
Q_snprintf( rwPath, sizeof( rwPath ), "%s/%s/", fs_rootdir, dirs.strings[i] );
FS_CreatePath( rwPath );
// revert to base game directory
if( has_base_dir )
Q_strncpy( fs_gamedir, fs_basedir, sizeof( fs_gamedir ));
}
}
stringlistfreecontents( &dirs );
}
// validate directories
stringlistinit( &dirs );
listdirectory( &dirs, "./" );
stringlistsort( &dirs );
for( i = 0; i < dirs.numstrings; i++ )
{
if( !Q_stricmp( fs_basedir, dirs.strings[i] ))
hasBaseDir = true;
if( !Q_stricmp( fs_gamedir, dirs.strings[i] ))
hasGameDir = true;
}
if( !hasGameDir )
{
Con_Printf( S_ERROR "game directory \"%s\" not exist\n", fs_gamedir );
if( hasBaseDir ) Q_strncpy( fs_gamedir, fs_basedir, sizeof( fs_gamedir ));
}
// build list of game directories here
// now start building first level of directory hierarchy
if( COM_CheckStringEmpty( fs_rodir ))
{
Q_snprintf( buf, sizeof( buf ), "%s/", fs_rodir );
@@ -1567,15 +1597,62 @@ qboolean FS_InitStdio( qboolean unused_set_to_true, const char *rootdir, const c
}
FS_AddGameDirectory( "./", FS_STATIC_PATH );
// but scan rodir for games first
if( COM_CheckStringEmpty( fs_rodir ))
{
stringlistinit( &dirs );
listdirectory( &dirs, fs_rodir, true );
stringlistsort( &dirs );
for( i = 0; i < dirs.numstrings; i++ )
{
if( !FS_SysFolderExists( dirs.strings[i] ))
continue;
if( FI.games[FI.numgames] == NULL )
FI.games[FI.numgames] = Mem_Malloc( fs_mempool, sizeof( *FI.games[FI.numgames] ));
if( FS_ParseGameInfo( dirs.strings[i], FI.games[FI.numgames], true ))
FI.numgames++;
}
stringlistfreecontents( &dirs );
}
rodir_num_games = FI.numgames;
stringlistinit( &dirs );
listdirectory( &dirs, "./", true );
stringlistsort( &dirs );
for( i = 0; i < dirs.numstrings; i++ )
{
int j;
if( !FS_SysFolderExists( dirs.strings[i] ))
continue;
if( FI.games[FI.numgames] == NULL )
FI.games[FI.numgames] = (gameinfo_t *)Mem_Calloc( fs_mempool, sizeof( gameinfo_t ));
// update gameinfo from rwdir, if it's newer
// with rodir we should never create new gameinfos anymore,
// so this only catches possible user changes
for( j = 0; j < rodir_num_games; j++ )
{
if( !Q_stricmp( dirs.strings[i], FI.games[j]->gamefolder ))
{
gameinfo_t gi;
if( FS_ParseGameInfo( dirs.strings[i], &gi, false ))
{
if( gi.mtime > FI.games[j]->mtime )
*FI.games[j] = gi;
}
break;
}
}
if( FS_ParseGameInfo( dirs.strings[i], FI.games[FI.numgames] ))
if( FI.games[FI.numgames] == NULL )
FI.games[FI.numgames] = Mem_Calloc( fs_mempool, sizeof( *FI.games[FI.numgames] ));
if( FS_ParseGameInfo( dirs.strings[i], FI.games[FI.numgames], false ))
FI.numgames++; // added
}
@@ -2628,25 +2705,20 @@ qboolean CRC32_File( dword *crcvalue, const char *filename )
qboolean MD5_HashFile( byte digest[16], const char *pszFileName, uint seed[4] )
{
file_t *file;
byte buffer[1024];
MD5Context_t MD5_Hash;
int bytes;
MD5Context_t MD5_Hash = { 0 };
if(( file = FS_Open( pszFileName, "rb", false )) == NULL )
return false;
memset( &MD5_Hash, 0, sizeof( MD5Context_t ));
MD5Init( &MD5_Hash );
if( seed )
{
MD5Update( &MD5_Hash, (const byte *)seed, 16 );
}
while( 1 )
{
bytes = FS_Read( file, buffer, sizeof( buffer ));
byte buffer[1024];
int bytes = FS_Read( file, buffer, sizeof( buffer ));
if( bytes > 0 )
MD5Update( &MD5_Hash, buffer, bytes );
@@ -3187,6 +3259,8 @@ const fs_api_t g_api =
FS_LoadFileFromArchive,
FS_GetRootDirectory,
FS_MakeGameInfo,
};
int EXPORT GetFSAPI( int version, fs_api_t *api, fs_globals_t **globals, fs_interface_t *engfuncs );

View File

@@ -123,6 +123,9 @@ typedef struct gameinfo_s
qboolean animated_title;
char demomap[MAX_QPATH];
qboolean rodir; // if true, parsed from rodir
int64_t mtime;
} gameinfo_t;
typedef struct fs_dllinfo_t
@@ -226,6 +229,8 @@ typedef struct fs_api_t
// gets current root directory, set by InitStdio
qboolean (*GetRootDirectory)( char *path, size_t size );
void (*MakeGameInfo)( void );
} fs_api_t;
typedef struct fs_interface_t

View File

@@ -205,7 +205,7 @@ void stringlistinit( stringlist_t *list );
void stringlistfreecontents( stringlist_t *list );
void stringlistappend( stringlist_t *list, const char *text );
void stringlistsort( stringlist_t *list );
void listdirectory( stringlist_t *list, const char *path );
void listdirectory( stringlist_t *list, const char *path, qboolean dirs_only );
// filesystem ops
int FS_FileExists( const char *filename, int gamedironly );
@@ -230,6 +230,7 @@ qboolean FS_FullPathToRelativePath( char *dst, const char *src, size_t size );
//
// pak.c
//
qboolean FS_CheckForQuakePak( const char *pakfile, const char *files[], size_t num_files );
searchpath_t *FS_AddPak_Fullpath( const char *pakfile, int flags );
//

View File

@@ -105,6 +105,8 @@ static pack_t *FS_LoadPackPAK( const char *packfile, int *error )
// TODO: use FS_Open to allow PK3 to be included into other archives
// Currently, it doesn't work with rodir due to FS_FindFile logic
// when it will use FS_Open, check that FS_CheckForQuakePak correctly
// detects Quake gamedirs in RoDir
packhandle = FS_SysOpen( packfile, "rb" );
if( packhandle == NULL )
@@ -364,3 +366,45 @@ searchpath_t *FS_AddPak_Fullpath( const char *pakfile, int flags )
return search;
}
/*
================
FS_CheckForQuakePak
To generate fake gameinfo for Quake directory, we need to parse pak0.pak
and find progs.dat in it
================
*/
qboolean FS_CheckForQuakePak( const char *pakfile, const char *files[], size_t num_files )
{
qboolean is_quake = false;
pack_t *pak;
int i;
pak = FS_LoadPackPAK( pakfile, NULL );
if( !pak )
return false;
for( i = 0; i < num_files; i++ )
{
int j;
for( j = 0; j < pak->numfiles; j++ )
{
if( Q_strchr( pak->files[j].name, '/' ))
continue; // exclude subdirectories
if( !Q_stricmp( pak->files[j].name, files[i] ))
{
is_quake = true;
break;
}
}
if( is_quake )
break;
}
Mem_Free( pak );
return is_quake;
}

View File

@@ -4,6 +4,11 @@ MEMFD_CREATE_TEST = '''#define _GNU_SOURCE
#include <sys/mman.h>
int main(int argc, char **argv) { return memfd_create(argv[0], 0); }'''
DIRENT_D_TYPE_TEST = '''#define _GNU_SOURCE
#include <dirent.h>
int main(int argc, char **argv) { struct dirent entry; entry.d_type = DT_DIR; return 0; }
'''
def options(opt):
pass
@@ -18,6 +23,9 @@ def configure(conf):
if conf.check_cc(fragment=MEMFD_CREATE_TEST, msg='Checking for memfd_create', mandatory=False):
conf.define('HAVE_MEMFD_CREATE', 1)
if conf.check_cc(fragment=DIRENT_D_TYPE_TEST, msg='Checking for d_type field in struct dirent', mandatory=False):
conf.define('HAVE_DIRENT_D_TYPE', 1)
def build(bld):
bld(name = 'filesystem_includes', export_includes = '.')

View File

@@ -171,7 +171,7 @@ _inline int Sys_Start( void )
if( Xash_Shutdown )
changeGame = Sys_ChangeGame;
ret = Xash_Main( szArgc, szArgv, szGameDir, 0, changeGame );
ret = Xash_Main( szArgc, szArgv, szGameDir, 0, XASH_DISABLE_MENU_CHANGEGAME ? NULL : changeGame );
Sys_UnloadEngine();

View File

@@ -18,7 +18,7 @@ def configure(conf):
if conf.env.DEST_OS == 'win32':
conf.load('winres')
conf.define_cond('XASH_DISABLE_MENU_CHANGEGAME', conf.options.DISABLE_MENU_CHANGEGAME)
conf.define('XASH_DISABLE_MENU_CHANGEGAME', conf.options.DISABLE_MENU_CHANGEGAME)
def build(bld):
source = ['game.cpp']

View File

@@ -239,29 +239,3 @@ const char *Q_buildarch( void )
);
}
/*
=============
Q_buildcommit
Returns a short hash of current commit in VCS as string
XASH_BUILD_COMMIT must be passed in quotes
=============
*/
const char *Q_buildcommit( void )
{
return XASH_BUILD_COMMIT;
}
/*
=============
Q_buildbranch
Returns current branch name in VCS as string
XASH_BUILD_BRANCH must be passed in quotes
=============
*/
const char *Q_buildbranch( void )
{
return XASH_BUILD_BRANCH;
}

18
public/build_vcs.c Normal file
View File

@@ -0,0 +1,18 @@
/*
build_vcs.c - info from VCS
Copyright (C) 2025 Alibek Omarov
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
*/
const char *g_buildcommit = XASH_BUILD_COMMIT;
const char *g_buildbranch = XASH_BUILD_BRANCH;

View File

@@ -663,21 +663,6 @@ void COM_RemoveLineFeed( char *str, size_t bufsize )
}
}
/*
============
COM_FixSlashes
Changes all '\' characters into '/' characters, in place.
============
*/
void COM_FixSlashes( char *pname )
{
while(( pname = Q_strchr( pname, '\\' )))
{
*pname = '/';
}
}
/*
============
COM_PathSlashFix

View File

@@ -62,8 +62,8 @@ const char *Q_PlatformStringByID( const int platform );
const char *Q_buildos( void );
const char *Q_ArchitectureStringByID( const int arch, const uint abi, const int endianness, const qboolean is64 );
const char *Q_buildarch( void );
const char *Q_buildcommit( void );
const char *Q_buildbranch( void );
extern const char *g_buildcommit;
extern const char *g_buildbranch;
//
// crtlib.c
@@ -96,7 +96,6 @@ void COM_ExtractFilePath( const char *path, char *dest );
const char *COM_FileWithoutPath( const char *in );
void COM_StripExtension( char *path );
void COM_RemoveLineFeed( char *str, size_t bufsize );
void COM_FixSlashes( char *pname );
void COM_PathSlashFix( char *path );
// return 0 on empty or null string, 1 otherwise
#define COM_CheckString( string ) ( ( !string || !*string ) ? 0 : 1 )
@@ -320,6 +319,19 @@ static inline int Q_splitstr( char *str, int delim, void *userdata,
return ret;
}
/*
============
COM_FixSlashes
Changes all '\' characters into '/' characters, in place.
============
*/
static inline void COM_FixSlashes( char *pname )
{
while(( pname = Q_strchr( pname, '\\' )))
*pname = '/';
}
#ifdef __cplusplus
}
#endif

View File

@@ -67,8 +67,6 @@ def options(opt):
def configure(conf):
# private to libpublic
conf.load('gitversion')
conf.define('XASH_BUILD_COMMIT', conf.env.GIT_VERSION if conf.env.GIT_VERSION else 'unknown-commit')
conf.define('XASH_BUILD_BRANCH', conf.env.GIT_BRANCH if conf.env.GIT_BRANCH else 'unknown-branch')
conf.env.VALIDATE_TARGET = conf.options.VALIDATE_TARGET
# need to expose it for everyone using libpublic headers
@@ -120,9 +118,14 @@ def build(bld):
export_includes = '. ../common ../pm_shared ../engine',
export_defines = bld.env.EXPORT_DEFINES_LIST)
bld.stlib(source = bld.path.ant_glob('*.c'),
target = 'public',
use = 'sdk_includes werror')
# build it separately to slightly improve rebuild times
bld.stlib(source = 'build_vcs.c',
target = 'build_vcs',
defines = ['XASH_BUILD_COMMIT=\"%s\"' % bld.env.GIT_VERSION, 'XASH_BUILD_BRANCH=\"%s\"' % bld.env.GIT_BRANCH])
bld.stlib(source = bld.path.ant_glob('*.c', excl='build_vcs.c'),
target = 'public',
use = 'sdk_includes werror build_vcs')
if bld.env.TESTS:
if bld.env.VALIDATE_TARGET:

View File

@@ -670,10 +670,14 @@ static void R_DecalNodeSurfaces( model_t *model, mnode_t *node, decalinfo_t *dec
// iterate over all surfaces in the node
msurface_t *surf;
int i;
int firstsurface, numsurfaces;
surf = model->surfaces + node->firstsurface;
firstsurface = node_firstsurface( node, model );
numsurfaces = node_numsurfaces( node, model );
for( i = 0; i < node->numsurfaces; i++, surf++ )
surf = model->surfaces + firstsurface;
for( i = 0; i < numsurfaces; i++, surf++ )
{
// never apply decals on the water or sky surfaces
if( surf->flags & (SURF_DRAWTURB|SURF_DRAWSKY|SURF_CONVEYOR))
@@ -695,6 +699,7 @@ static void R_DecalNode( model_t *model, mnode_t *node, decalinfo_t *decalinfo )
{
mplane_t *splitplane;
float dist;
mnode_t *children[2];
Assert( node != NULL );
@@ -706,6 +711,7 @@ static void R_DecalNode( model_t *model, mnode_t *node, decalinfo_t *decalinfo )
splitplane = node->plane;
dist = DotProduct( decalinfo->m_Position, splitplane->normal ) - splitplane->dist;
node_children( children, node, model );
// This is arbitrarily set to 10 right now. In an ideal world we'd have the
// exact surface but we don't so, this tells me which planes are "sort of
@@ -717,19 +723,19 @@ static void R_DecalNode( model_t *model, mnode_t *node, decalinfo_t *decalinfo )
// have a surface normal
if( dist > decalinfo->m_Size )
{
R_DecalNode( model, node->children[0], decalinfo );
R_DecalNode( model, children[0], decalinfo );
}
else if( dist < -decalinfo->m_Size )
{
R_DecalNode( model, node->children[1], decalinfo );
R_DecalNode( model, children[1], decalinfo );
}
else
{
if( dist < DECAL_DISTANCE && dist > -DECAL_DISTANCE )
R_DecalNodeSurfaces( model, node, decalinfo );
R_DecalNode( model, node->children[0], decalinfo );
R_DecalNode( model, node->children[1], decalinfo );
R_DecalNode( model, children[0], decalinfo );
R_DecalNode( model, children[1], decalinfo );
}
}

View File

@@ -502,11 +502,11 @@ void R_ClearSkyBox( void );
void R_DrawSkyBox( void );
void R_DrawClouds( void );
void R_UnloadSkybox( void );
void EmitWaterPolys( msurface_t *warp, qboolean reverse );
void EmitWaterPolys( msurface_t *warp, qboolean reverse, qboolean ripples );
void R_InitRipples( void );
void R_ResetRipples( void );
void R_AnimateRipples( void );
void R_UploadRipples( texture_t *image );
qboolean R_UploadRipples( const texture_t *image );
//#include "vid_common.h"

View File

@@ -104,27 +104,33 @@ void R_MarkLights( dlight_t *light, int bit, mnode_t *node )
float dist;
msurface_t *surf;
int i;
mnode_t *children[2];
int firstsurface, numsurfaces;
if( !node || node->contents < 0 )
return;
dist = PlaneDiff( light->origin, node->plane );
node_children( children, node, RI.currentmodel );
firstsurface = node_firstsurface( node, RI.currentmodel );
numsurfaces = node_numsurfaces( node, RI.currentmodel );
if( dist > light->radius )
{
R_MarkLights( light, bit, node->children[0] );
R_MarkLights( light, bit, children[0] );
return;
}
if( dist < -light->radius )
{
R_MarkLights( light, bit, node->children[1] );
R_MarkLights( light, bit, children[1] );
return;
}
// mark the polygons
surf = RI.currentmodel->surfaces + node->firstsurface;
surf = RI.currentmodel->surfaces + firstsurface;
for( i = 0; i < node->numsurfaces; i++, surf++ )
for( i = 0; i < numsurfaces; i++, surf++ )
{
if( !BoundsAndSphereIntersect( surf->info->mins, surf->info->maxs, light->origin, light->radius ))
continue; // no intersection
@@ -137,8 +143,8 @@ void R_MarkLights( dlight_t *light, int bit, mnode_t *node )
surf->dlightbits |= bit;
}
R_MarkLights( light, bit, node->children[0] );
R_MarkLights( light, bit, node->children[1] );
R_MarkLights( light, bit, children[0] );
R_MarkLights( light, bit, children[1] );
}
/*
@@ -202,6 +208,8 @@ static qboolean R_RecursiveLightPoint( model_t *model, mnode_t *node, float p1f,
mtexinfo_t *tex;
matrix3x4 tbn;
vec3_t mid;
mnode_t *children[2];
int firstsurface, numsurfaces;
// didn't hit anything
if( !node || node->contents < 0 )
@@ -210,13 +218,17 @@ static qboolean R_RecursiveLightPoint( model_t *model, mnode_t *node, float p1f,
return false;
}
node_children( children, node, model );
firstsurface = node_firstsurface( node, model );
numsurfaces = node_numsurfaces( node, model );
// calculate mid point
front = PlaneDiff( start, node->plane );
back = PlaneDiff( end, node->plane );
side = front < 0;
if(( back < 0 ) == side )
return R_RecursiveLightPoint( model, node->children[side], p1f, p2f, cv, start, end );
return R_RecursiveLightPoint( model, children[side], p1f, p2f, cv, start, end );
frac = front / ( front - back );
@@ -224,7 +236,7 @@ static qboolean R_RecursiveLightPoint( model_t *model, mnode_t *node, float p1f,
midf = p1f + ( p2f - p1f ) * frac;
// co down front side
if( R_RecursiveLightPoint( model, node->children[side], p1f, midf, cv, start, mid ))
if( R_RecursiveLightPoint( model, children[side], p1f, midf, cv, start, mid ))
return true; // hit something
if(( back < 0 ) == side )
@@ -234,10 +246,10 @@ static qboolean R_RecursiveLightPoint( model_t *model, mnode_t *node, float p1f,
}
// check for impact on this node
surf = model->surfaces + node->firstsurface;
surf = model->surfaces + firstsurface;
VectorCopy( mid, g_trace_lightspot );
for( i = 0; i < node->numsurfaces; i++, surf++ )
for( i = 0; i < numsurfaces; i++, surf++ )
{
int smax, tmax;
@@ -326,7 +338,7 @@ static qboolean R_RecursiveLightPoint( model_t *model, mnode_t *node, float p1f,
}
// go down back side
return R_RecursiveLightPoint( model, node->children[!side], midf, p2f, cv, mid, end );
return R_RecursiveLightPoint( model, children[!side], midf, p2f, cv, mid, end );
}
/*

View File

@@ -600,6 +600,7 @@ watertexture to grab fog values from it
static gl_texture_t *R_RecursiveFindWaterTexture( const mnode_t *node, const mnode_t *ignore, qboolean down )
{
gl_texture_t *tex = NULL;
mnode_t *children[2];
// assure the initial node is not null
// we could check it here, but we would rather check it
@@ -637,15 +638,17 @@ static gl_texture_t *R_RecursiveFindWaterTexture( const mnode_t *node, const mno
// this is a regular node
// traverse children
if( node->children[0] && ( node->children[0] != ignore ))
node_children( children, node, WORLDMODEL );
if( children[0] && ( children[0] != ignore ))
{
tex = R_RecursiveFindWaterTexture( node->children[0], node, true );
tex = R_RecursiveFindWaterTexture( children[0], node, true );
if( tex ) return tex;
}
if( node->children[1] && ( node->children[1] != ignore ))
if( children[1] && ( children[1] != ignore ))
{
tex = R_RecursiveFindWaterTexture( node->children[1], node, true );
tex = R_RecursiveFindWaterTexture( children[1], node, true );
if( tex ) return tex;
}

View File

@@ -125,12 +125,25 @@ static void R_TextureCoord( const vec3_t v, const msurface_t *surf, vec2_t coord
static void R_GetEdgePosition( const model_t *mod, const msurface_t *fa, int i, vec3_t vec )
{
const int lindex = mod->surfedges[fa->firstedge + i];
const medge_t *pedges = mod->edges;
if( lindex > 0 )
VectorCopy( mod->vertexes[pedges[lindex].v[0]].position, vec );
if( FBitSet( mod->flags, MODEL_QBSP2 ))
{
const medge32_t *pedges = mod->edges32;
if( lindex > 0 )
VectorCopy( mod->vertexes[pedges[lindex].v[0]].position, vec );
else
VectorCopy( mod->vertexes[pedges[-lindex].v[1]].position, vec );
}
else
VectorCopy( mod->vertexes[pedges[-lindex].v[1]].position, vec );
{
const medge16_t *pedges = mod->edges16;
if( lindex > 0 )
VectorCopy( mod->vertexes[pedges[lindex].v[0]].position, vec );
else
VectorCopy( mod->vertexes[pedges[-lindex].v[1]].position, vec );
}
}
static void BoundPoly( int numverts, float *verts, vec3_t mins, vec3_t maxs )
@@ -1317,10 +1330,8 @@ static void R_RenderBrushPoly( msurface_t *fa, int cull_type )
if( FBitSet( fa->flags, SURF_DRAWTURB ))
{
R_UploadRipples( t );
// warp texture, no lightmaps
EmitWaterPolys( fa, (cull_type == CULL_BACKSIDE));
EmitWaterPolys( fa, cull_type == CULL_BACKSIDE, R_UploadRipples( t ));
return;
}
else GL_Bind( XASH_TEXTURE0, t->gl_texturenum );
@@ -1500,11 +1511,8 @@ void R_DrawWaterSurfaces( void )
if( !FBitSet( s->flags, SURF_DRAWTURB ))
continue;
// set modulate mode explicitly
R_UploadRipples( t );
for( ; s; s = s->texturechain )
EmitWaterPolys( s, false );
EmitWaterPolys( s, false, R_UploadRipples( t ));
t->texturechain = NULL;
}
@@ -1847,7 +1855,7 @@ typedef struct vbodecaldata_s
// gl_decals.c
extern decal_t gDecalPool[MAX_RENDER_DECALS];
struct vbo_static_s
static struct vbo_static_s
{
// quickly free all allocations on map change
poolhandle_t mempool;
@@ -1883,7 +1891,7 @@ struct vbo_static_s
qboolean enabled;
} vbos;
struct multitexturestate_s
static struct multitexturestate_s
{
int tmu_gl; // texture tmu
int tmu_dt; // detail tmu
@@ -3311,6 +3319,9 @@ static void R_RecursiveWorldNode( mnode_t *node, uint clipflags )
mleaf_t *pleaf;
int c, side;
float dot;
mnode_t *children[2];
int numsurfaces, firstsurface;
loc0:
if( node->contents == CONTENTS_SOLID )
return; // hit a solid leaf
@@ -3365,10 +3376,14 @@ loc0:
side = (dot >= 0.0f) ? 0 : 1;
// recurse down the children, front side first
R_RecursiveWorldNode( node->children[side], clipflags );
node_children( children, node, WORLDMODEL );
R_RecursiveWorldNode( children[side], clipflags );
firstsurface = node_firstsurface( node, WORLDMODEL );
numsurfaces = node_numsurfaces( node, WORLDMODEL );
// draw stuff
for( c = node->numsurfaces, surf = WORLDMODEL->surfaces + node->firstsurface; c; c--, surf++ )
for( c = numsurfaces, surf = WORLDMODEL->surfaces + firstsurface; c; c--, surf++ )
{
if( R_CullSurface( surf, &RI.frustum, clipflags ))
continue;
@@ -3387,7 +3402,7 @@ loc0:
}
// recurse down the back side
node = node->children[!side];
node = children[!side];
goto loc0;
}
@@ -3463,6 +3478,9 @@ static void R_DrawWorldTopView( mnode_t *node, uint clipflags )
do
{
mnode_t *children[2];
int numsurfaces, firstsurface;
if( node->contents == CONTENTS_SOLID )
return; // hit a solid leaf
@@ -3496,7 +3514,10 @@ static void R_DrawWorldTopView( mnode_t *node, uint clipflags )
}
// draw stuff
for( c = node->numsurfaces, surf = WORLDMODEL->surfaces + node->firstsurface; c; c--, surf++ )
numsurfaces = node_numsurfaces( node, WORLDMODEL );
firstsurface = node_firstsurface( node, WORLDMODEL );
for( c = numsurfaces, surf = WORLDMODEL->surfaces + firstsurface; c; c--, surf++ )
{
// don't process the same surface twice
if( surf->visframe == tr.framecount )
@@ -3515,9 +3536,9 @@ static void R_DrawWorldTopView( mnode_t *node, uint clipflags )
}
// recurse down both children, we don't care the order...
R_DrawWorldTopView( node->children[0], clipflags );
node = node->children[1];
node_children( children, node, WORLDMODEL );
R_DrawWorldTopView( children[0], clipflags );
node = children[1];
} while( node );
}

View File

@@ -81,7 +81,6 @@ static struct
uint32_t texture[RIPPLES_TEXSIZE];
int gl_texturenum;
int rippletexturenum;
float texturescale; // not all textures are 128x128, scale the texcoords down
} g_ripple;
@@ -565,7 +564,7 @@ EmitWaterPolys
Does a water warp on the pre-fragmented glpoly_t chain
=============
*/
void EmitWaterPolys( msurface_t *warp, qboolean reverse )
void EmitWaterPolys( msurface_t *warp, qboolean reverse, qboolean ripples )
{
float *v, nv, waveHeight;
float s, t, os, ot;
@@ -609,15 +608,15 @@ void EmitWaterPolys( msurface_t *warp, qboolean reverse )
os = v[3];
ot = v[4];
if( !r_ripple.value )
if( !ripples )
{
s = os + r_turbsin[(int)((ot * 0.125f + gp_cl->time) * TURBSCALE) & 255];
t = ot + r_turbsin[(int)((os * 0.125f + gp_cl->time) * TURBSCALE) & 255];
}
else
{
s = os / g_ripple.texturescale;
t = ot / g_ripple.texturescale;
s = os;
t = ot;
}
s *= ( 1.0f / SUBDIVIDE_SIZE );
@@ -710,17 +709,6 @@ static void R_RunRipplesAnimation( const short *oldbuf, short *pbuf )
}
}
static int MostSignificantBit( unsigned int v )
{
#if __GNUC__
return 31 - __builtin_clz( v );
#else
int i;
for( i = 0, v >>= 1; v; v >>= 1, i++ );
return i;
#endif
}
void R_AnimateRipples( void )
{
double frametime = gp_cl->time - g_ripple.time;
@@ -750,67 +738,72 @@ void R_AnimateRipples( void )
R_RunRipplesAnimation( g_ripple.oldbuf, g_ripple.curbuf );
}
void R_UploadRipples( texture_t *image )
qboolean R_UploadRipples( const texture_t *image )
{
gl_texture_t *glt;
uint32_t *pixels;
int wbits, wmask, wshft;
const gl_texture_t *glt;
const uint32_t *pixels;
int y;
int width, height, size;
// discard unuseful textures
if( !r_ripple.value || image->width > RIPPLES_CACHEWIDTH || image->width != image->height )
if( !r_ripple.value )
{
GL_Bind( XASH_TEXTURE0, image->gl_texturenum );
return;
return false;
}
// discard unuseful textures
glt = R_GetTexture( image->gl_texturenum );
if( !glt || !glt->original || !glt->original->buffer || !FBitSet( glt->flags, TF_EXPAND_SOURCE ))
{
GL_Bind( XASH_TEXTURE0, image->gl_texturenum );
return;
return false;
}
GL_Bind( XASH_TEXTURE0, g_ripple.rippletexturenum );
// no updates this frame
if( !g_ripple.update && image->gl_texturenum == g_ripple.gl_texturenum )
return;
return true;
g_ripple.gl_texturenum = image->gl_texturenum;
if( r_ripple.value == 1.0f )
{
g_ripple.texturescale = Q_max( 1.0f, image->width / 64.0f );
}
else
{
g_ripple.texturescale = 1.0f;
}
size = r_ripple.value == 1.0f ? 64 : RIPPLES_CACHEWIDTH;
pixels = (uint32_t *)glt->original->buffer;
wbits = MostSignificantBit( image->width );
wshft = 7 - wbits;
wmask = image->width - 1;
// try to preserve aspect ratio
width = height = RIPPLES_CACHEWIDTH; // always render at maximum size
if( image->width > image->height )
height = (float)image->height / image->width * width;
else if( image->width < image->height )
width = (float)image->width / image->height * height;
for( y = 0; y < image->height; y++ )
pixels = (const uint32_t *)glt->original->buffer;
for( y = 0; y < height; y++ )
{
int ry = y << ( 7 + wshft );
int ry = (float)y / height * size;
int x;
for( x = 0; x < image->width; x++ )
for( x = 0; x < width; x++ )
{
int rx = x << wshft;
int val = g_ripple.curbuf[ry + rx] >> 4;
int rx = (float)x / width * size;
int val = g_ripple.curbuf[ry * RIPPLES_CACHEWIDTH + rx] / 16;
int py = (y - val) & wmask;
int px = (x + val) & wmask;
int p = ( py << wbits ) + px;
// transform it to texture space and get nice tiling effect
int rpy = ( y - val ) % height;
int rpx = ( x + val ) % width;
g_ripple.texture[(y << wbits) + x] = pixels[p];
int py = (float)rpy / height * image->height;
int px = (float)rpx / width * image->width;
if( py < 0 ) py = image->height + py;
if( px < 0 ) px = image->width + px;
g_ripple.texture[y * width + x] = pixels[py * image->width + px];
}
}
pglTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, image->width, image->width, 0,
pglTexImage2D( GL_TEXTURE_2D, 0, glt->format, width, height, 0,
GL_RGBA, GL_UNSIGNED_BYTE, g_ripple.texture );
return true;
}

View File

@@ -175,7 +175,7 @@ void R_RotateBmodel( void )
R_RecursiveClipBPoly
================
*/
static void R_RecursiveClipBPoly( bedge_t *pedges, mnode_t *pnode, msurface_t *psurf )
static void R_RecursiveClipBPoly( model_t *mod, bedge_t *pedges, mnode_t *pnode, msurface_t *psurf )
{
bedge_t *psideedges[2], *pnextedge, *ptedge;
int i, side, lastside;
@@ -316,7 +316,7 @@ static void R_RecursiveClipBPoly( bedge_t *pedges, mnode_t *pnode, msurface_t *p
{
// draw if we've reached a non-solid leaf, done if all that's left is a
// solid leaf, and continue down the tree if it's not a leaf
pn = pnode->children[i];
pn = node_child( pnode, i, mod );
// we're done with this branch if the node or leaf isn't in the PVS
if( pn->visframe == tr.visframecount )
@@ -332,8 +332,7 @@ static void R_RecursiveClipBPoly( bedge_t *pedges, mnode_t *pnode, msurface_t *p
}
else
{
R_RecursiveClipBPoly( psideedges[i], pnode->children[i],
psurf );
R_RecursiveClipBPoly( mod, psideedges[i], pn, psurf );
}
}
}
@@ -356,13 +355,13 @@ void R_DrawSolidClippedSubmodelPolygons( model_t *pmodel, mnode_t *topnode )
mplane_t *pplane;
mvertex_t bverts[MAX_BMODEL_VERTS];
bedge_t bedges[MAX_BMODEL_EDGES], *pbedge;
medge_t *pedge, *pedges;
medge16_t *pedge, *pedges;
// FIXME: use bounding-box-based frustum clipping info?
psurf = &pmodel->surfaces[pmodel->firstmodelsurface];
numsurfaces = pmodel->nummodelsurfaces;
pedges = pmodel->edges;
pedges = pmodel->edges16;
for( i = 0; i < numsurfaces; i++, psurf++ )
{
@@ -419,7 +418,7 @@ void R_DrawSolidClippedSubmodelPolygons( model_t *pmodel, mnode_t *topnode )
pbedge[j - 1].pnext = NULL; // mark end of edges
// if ( !( psurf->texinfo->flags & ( SURF_TRANS66 | SURF_TRANS33 ) ) )
R_RecursiveClipBPoly( pbedge, topnode, psurf );
R_RecursiveClipBPoly( pmodel, pbedge, topnode, psurf );
// else
// R_RenderBmodelFace( pbedge, psurf );
}
@@ -566,6 +565,9 @@ static void R_RecursiveWorldNode( mnode_t *node, int clipflags )
}
else
{
mnode_t *children[2];
int firstsurface;
// node is just a decision point, so go down the apropriate sides
// find which side of the node we are on
@@ -593,14 +595,16 @@ static void R_RecursiveWorldNode( mnode_t *node, int clipflags )
side = 1;
// recurse down the children, front side first
R_RecursiveWorldNode( node->children[side], clipflags );
node_children( children, node, WORLDMODEL );
R_RecursiveWorldNode( children[side], clipflags );
// draw stuff
c = node->numsurfaces;
c = node_numsurfaces( node, WORLDMODEL );
firstsurface = node_firstsurface( node, WORLDMODEL );
if( c )
{
surf = WORLDMODEL->surfaces + node->firstsurface;
surf = WORLDMODEL->surfaces + firstsurface;
if( dot < -BACKFACE_EPSILON )
{
@@ -636,7 +640,7 @@ static void R_RecursiveWorldNode( mnode_t *node, int clipflags )
}
// recurse down the back side
R_RecursiveWorldNode( node->children[!side], clipflags );
R_RecursiveWorldNode( children[!side], clipflags );
}
}

View File

@@ -679,23 +679,24 @@ static void R_DecalSurface( msurface_t *surf, decalinfo_t *decalinfo )
static void R_DecalNodeSurfaces( model_t *model, mnode_t *node, decalinfo_t *decalinfo )
{
// iterate over all surfaces in the node
msurface_t *surf;
int i;
msurface_t *surf;
int i;
int firstsurface, numsurfaces;
surf = model->surfaces + node->firstsurface;
firstsurface = node_firstsurface( node, model );
numsurfaces = node_numsurfaces( node, model );
for( i = 0; i < node->numsurfaces; i++, surf++ )
surf = model->surfaces + firstsurface;
for( i = 0; i < numsurfaces; i++, surf++ )
{
// never apply decals on the water or sky surfaces
if( surf->flags & ( SURF_DRAWTURB | SURF_DRAWSKY | SURF_CONVEYOR ))
if( surf->flags & (SURF_DRAWTURB|SURF_DRAWSKY|SURF_CONVEYOR))
continue;
// we can implement alpha testing without stencil
// if( surf->flags & SURF_TRANSPARENT && !glState.stencilEnabled )
// continue;
R_DecalSurface( surf, decalinfo );
}
}
// -----------------------------------------------------------------------------
@@ -707,6 +708,7 @@ static void R_DecalNode( model_t *model, mnode_t *node, decalinfo_t *decalinfo )
{
mplane_t *splitplane;
float dist;
mnode_t *children[2];
Assert( node != NULL );
@@ -718,6 +720,7 @@ static void R_DecalNode( model_t *model, mnode_t *node, decalinfo_t *decalinfo )
splitplane = node->plane;
dist = DotProduct( decalinfo->m_Position, splitplane->normal ) - splitplane->dist;
node_children( children, node, model );
// This is arbitrarily set to 10 right now. In an ideal world we'd have the
// exact surface but we don't so, this tells me which planes are "sort of
@@ -729,19 +732,19 @@ static void R_DecalNode( model_t *model, mnode_t *node, decalinfo_t *decalinfo )
// have a surface normal
if( dist > decalinfo->m_Size )
{
R_DecalNode( model, node->children[0], decalinfo );
R_DecalNode( model, children[0], decalinfo );
}
else if( dist < -decalinfo->m_Size )
{
R_DecalNode( model, node->children[1], decalinfo );
R_DecalNode( model, children[1], decalinfo );
}
else
{
if( dist < DECAL_DISTANCE && dist > -DECAL_DISTANCE )
R_DecalNodeSurfaces( model, node, decalinfo );
R_DecalNode( model, node->children[0], decalinfo );
R_DecalNode( model, node->children[1], decalinfo );
R_DecalNode( model, children[0], decalinfo );
R_DecalNode( model, children[1], decalinfo );
}
}

View File

@@ -104,44 +104,50 @@ R_MarkLights
*/
void R_MarkLights( dlight_t *light, int bit, mnode_t *node )
{
float dist;
msurface_t *surf;
int i;
float dist;
msurface_t *surf;
int i;
mnode_t *children[2];
int firstsurface, numsurfaces;
if( !node || node->contents < 0 )
return;
dist = PlaneDiff( light->origin, node->plane );
node_children( children, node, RI.currentmodel );
firstsurface = node_firstsurface( node, RI.currentmodel );
numsurfaces = node_numsurfaces( node, RI.currentmodel );
if( dist > light->radius )
{
R_MarkLights( light, bit, node->children[0] );
R_MarkLights( light, bit, children[0] );
return;
}
if( dist < -light->radius )
{
R_MarkLights( light, bit, node->children[1] );
R_MarkLights( light, bit, children[1] );
return;
}
// mark the polygons
surf = RI.currentmodel->surfaces + node->firstsurface;
surf = RI.currentmodel->surfaces + firstsurface;
for( i = 0; i < node->numsurfaces; i++, surf++ )
for( i = 0; i < numsurfaces; i++, surf++ )
{
if( !BoundsAndSphereIntersect( surf->info->mins, surf->info->maxs, light->origin, light->radius ))
continue; // no intersection
continue; // no intersection
if( surf->dlightframe != tr.framecount ) // tr.dlightframecount )
if( surf->dlightframe != tr.dlightframecount )
{
surf->dlightbits = 0;
surf->dlightframe = tr.framecount; // tr.dlightframecount;
surf->dlightframe = tr.dlightframecount;
}
surf->dlightbits |= bit;
}
R_MarkLights( light, bit, node->children[0] );
R_MarkLights( light, bit, node->children[1] );
R_MarkLights( light, bit, children[0] );
R_MarkLights( light, bit, children[1] );
}
/*
@@ -205,6 +211,8 @@ static qboolean R_RecursiveLightPoint( model_t *model, mnode_t *node, float p1f,
mtexinfo_t *tex;
matrix3x4 tbn;
vec3_t mid;
mnode_t *children[2];
int firstsurface, numsurfaces;
// didn't hit anything
if( !node || node->contents < 0 )
@@ -213,13 +221,17 @@ static qboolean R_RecursiveLightPoint( model_t *model, mnode_t *node, float p1f,
return false;
}
node_children( children, node, model );
firstsurface = node_firstsurface( node, model );
numsurfaces = node_numsurfaces( node, model );
// calculate mid point
front = PlaneDiff( start, node->plane );
back = PlaneDiff( end, node->plane );
side = front < 0;
if(( back < 0 ) == side )
return R_RecursiveLightPoint( model, node->children[side], p1f, p2f, cv, start, end );
return R_RecursiveLightPoint( model, children[side], p1f, p2f, cv, start, end );
frac = front / ( front - back );
@@ -227,7 +239,7 @@ static qboolean R_RecursiveLightPoint( model_t *model, mnode_t *node, float p1f,
midf = p1f + ( p2f - p1f ) * frac;
// co down front side
if( R_RecursiveLightPoint( model, node->children[side], p1f, midf, cv, start, mid ))
if( R_RecursiveLightPoint( model, children[side], p1f, midf, cv, start, mid ))
return true; // hit something
if(( back < 0 ) == side )
@@ -237,10 +249,10 @@ static qboolean R_RecursiveLightPoint( model_t *model, mnode_t *node, float p1f,
}
// check for impact on this node
surf = model->surfaces + node->firstsurface;
surf = model->surfaces + firstsurface;
VectorCopy( mid, g_trace_lightspot );
for( i = 0; i < node->numsurfaces; i++, surf++ )
for( i = 0; i < numsurfaces; i++, surf++ )
{
int smax, tmax;
@@ -330,7 +342,7 @@ static qboolean R_RecursiveLightPoint( model_t *model, mnode_t *node, float p1f,
}
// go down back side
return R_RecursiveLightPoint( model, node->children[!side], midf, p2f, cv, mid, end );
return R_RecursiveLightPoint( model, children[!side], midf, p2f, cv, mid, end );
}
/*

View File

@@ -933,7 +933,7 @@ typedef struct edge_s
unsigned short surfs[2];
struct edge_s *nextremove;
float nearzi;
medge_t *owner;
medge16_t *owner;
} edge_t;

View File

@@ -524,6 +524,7 @@ watertexture to grab fog values from it
static image_t *R_RecursiveFindWaterTexture( const mnode_t *node, const mnode_t *ignore, qboolean down )
{
image_t *tex = NULL;
mnode_t *children[2];
// assure the initial node is not null
// we could check it here, but we would rather check it
@@ -561,18 +562,18 @@ static image_t *R_RecursiveFindWaterTexture( const mnode_t *node, const mnode_t
// this is a regular node
// traverse children
if( node->children[0] && ( node->children[0] != ignore ))
node_children( children, node, WORLDMODEL );
if( children[0] && ( children[0] != ignore ))
{
tex = R_RecursiveFindWaterTexture( node->children[0], node, true );
if( tex )
return tex;
tex = R_RecursiveFindWaterTexture( children[0], node, true );
if( tex ) return tex;
}
if( node->children[1] && ( node->children[1] != ignore ))
if( children[1] && ( children[1] != ignore ))
{
tex = R_RecursiveFindWaterTexture( node->children[1], node, true );
if( tex )
return tex;
tex = R_RecursiveFindWaterTexture( children[1], node, true );
if( tex ) return tex;
}
// for down recursion, return immediately
@@ -796,9 +797,9 @@ static mnode_t *R_FindTopnode( vec3_t mins, vec3_t maxs )
// not split yet; recurse down the contacted side
if( sides & 1 )
node = node->children[0];
node = node_child( node, 0, WORLDMODEL );
else
node = node->children[1];
node = node_child( node, 1, WORLDMODEL );
}
}
@@ -1392,6 +1393,12 @@ void GAME_EXPORT R_NewMap( void )
R_ClearDecals(); // clear all level decals
R_StudioResetPlayerModels();
if( FBitSet( world->flags, MODEL_QBSP2 ))
{
gEngfuncs.Host_Error( "Sorry, ref_soft can't load maps in BSP2 format.\n" );
return;
}
r_cnumsurfs = sw_maxsurfs.value;
if( r_cnumsurfs <= MINSURFACES )

View File

@@ -37,7 +37,7 @@ int c_faceclip; // numbe
clipplane_t *entity_clipplanes;
clipplane_t world_clipplanes[16];
medge_t *r_pedge;
medge16_t *r_pedge;
qboolean r_leftclipped, r_rightclipped;
static qboolean makeleftedge, makerightedge;
@@ -68,7 +68,7 @@ msurface_t *r_skyfaces;
mplane_t r_skyplanes[6];
mtexinfo_t r_skytexinfo[6];
mvertex_t *r_skyverts;
medge_t *r_skyedges;
medge16_t *r_skyedges;
int *r_skysurfedges;
// I just copied this data from a box map...
@@ -438,7 +438,7 @@ void R_RenderFace( msurface_t *fa, int clipflags )
mplane_t *pplane;
float distinv;
vec3_t p_normal;
medge_t *pedges, tedge;
medge16_t *pedges, tedge;
clipplane_t *pclip;
// translucent surfaces are not drawn by the edge renderer
@@ -490,7 +490,7 @@ void R_RenderFace( msurface_t *fa, int clipflags )
r_nearzi = 0;
r_nearzionly = false;
makeleftedge = makerightedge = false;
pedges = RI.currentmodel->edges;
pedges = RI.currentmodel->edges16;
r_lastvertvalid = false;
for( i = 0; i < fa->numedges; i++ )
@@ -560,7 +560,7 @@ void R_RenderFace( msurface_t *fa, int clipflags )
else
{
// it's cached if the cached edge is valid and is owned
// by this medge_t
// by this medge16_t
if((((uintptr_t)edge_p - (uintptr_t)r_edges )
> r_pedge->cachededgeoffset )
&& (((edge_t *)((uintptr_t)r_edges
@@ -651,7 +651,7 @@ void R_RenderBmodelFace( bedge_t *pedges, msurface_t *psurf )
mplane_t *pplane;
float distinv;
vec3_t p_normal;
medge_t tedge;
medge16_t tedge;
clipplane_t *pclip;
/*if (psurf->texinfo->flags & (SURF_TRANS33|SURF_TRANS66))

View File

@@ -11,14 +11,17 @@ fi
# NOTE: to build with other version use --msvc_version during configuration
# NOTE: sometimes you may need to add WinSDK to %PATH%
./waf.bat configure -s "SDL2_VC" -T release --enable-utils --enable-tests --enable-lto $AMD64 || die_configure
# NOTE: --enable-msvcdeps only used for CI builds, enabling it non-English versions of MSVC causes useless console spam
./waf.bat configure -s "SDL2_VC" -T release --enable-utils --enable-tests --enable-lto --enable-msvcdeps $AMD64 || die_configure
./waf.bat build || die
./waf.bat install --destdir=. || die
if [ "$ARCH" = "i386" ]; then
cp SDL2_VC/lib/x86/SDL2.dll . # Install SDL2
cp SDL2_VC/lib/x86/SDL2.pdb .
elif [ "$ARCH" = "amd64" ]; then
cp SDL2_VC/lib/x64/SDL2.dll .
cp SDL2_VC/lib/x64/SDL2.pdb .
else
die
fi

Some files were not shown because too many files have changed in this diff Show More