295 Commits
latest ... CBT

Author SHA1 Message Date
Eero
9d6cb5225a Refactor server event processing and entity updates 2026-05-02 00:28:12 +08:00
NotAlwaysTrue
8b6da6b033 Added a null check for STW
Changed positions of notes
2026-04-30 22:35:30 +08:00
NotAlwaysTrue
02689d0d86 Refactor single-thread worker
Re-parallel Hull Update
Use new gap shaffle algorithm
2026-04-30 20:05:23 +08:00
NotAlwaysTrue
099d664731 Fixed issues bring by update 2026-04-25 13:21:25 +08:00
NotAlwaysTrue
1f7d695bba Removed LuaSafeUserData to sync with upstream 2026-04-25 13:11:05 +08:00
NotAlwaysTrue
50327a4d83 Merge branch 'heads/upstream' into OBT/1.2.0(SpringUpdate) 2026-04-25 13:08:16 +08:00
NotAlwaysTrue
9b35f6b23f Sync with upstream
* Update bug-reports.yml

* Fix modifyChatMessage hook

* Add LuaCsSetup.Lua back for compatibility

* Fix Game.AssignOnExecute having command arguments be passed as varargs instead of a table

* Actually use the PackageId const everywhere we need to refer to our content package

* Load languages files even if the package is disabled

* Fix Hook.Remove not being implemented properly

* - Changed event aliases to be case insensitive.

* - Fixed assembly logging style.
- Fixed double logging during execution.

* Fix garbage network data being read by the game when reading LuaCs network messages

* PackageId -> PackageName

* Added caching toggle to PluginManagementService

* Fix LuaCs initializing too late for singleplayer campaigns and rework the C# prompt to only show when enabling mods/joining server

* Oops, fix NRE crash

* Fix hide username in logs config not doing anything

* Fix Cs prompt showing up more than one between rounds

* Fix server host being prompted twice with the C# popup

* Ignore our workshop packages from the game's dependency thing since it doesn't really make sense

* Load console commands after executing and possible fix for the not console command permitted

* Added fallback friendly name resolution for ModConfig assembly contents.

* Register Voronoi2 stuff

* Added configinfo null check to SettingBase.cs

* Add safety check so this stops crashing when we look at it the wrong way

* Fixed "Folder" attribute files not being found.

* Keep the LuaCsConfig class laying around for compatibility, not sure anywhere in our code base (and shouldn't be)

* Added fallback compilation for UseInternalsAwareAssembly if the publicized script compilation fails.

* Added legacy overload of AddCommand for mod compat.

* Added LoggerService to Lua env. Made ILoggerService compliant with LuaCsLogger API.

* Changed csharp script compilation algorithm to be best effort.

* Added "RunUnrestricted" mode for lua scripts that need to run outside of sandbox.

* - Fixed networking sync vars failing to sync initially.
- Fixed lua failing to differentiate overloads ISettingBase.

* Add alias for human.CPRSuccess and human.CPRFailed

* - Fixed up the settings menu.
- Made SettingEntry throw an error if "Value" attribute is not found in XML.
- Fixed saved values for settings sometimes not reloading after disabling and re-enabling a package.

* Fix LuaCs net messages received during connection initialization to be read incorrectly, happened because we would reset the BitPosition in our harmony patch which would cause the message to be read incorrectly later

* Allow reloadlua to force the state to running

* New icon for settings and make the top left text more user friendly

* Fix client.packages hook sending normal packages

* Fixed OnUpdate() not passing in deltaTime instead of totalTime.

* Missing diffs from bb21a09244

* Added networking tests for configs.

* Added missing diffs for f61f852a25.

* Some tweaks to the text

* Remove missing Value error, it should just use the default value if it's not specified

* Fix UseInternalAccessName

* Always purge cashes for plugin content on unloading.

* Fix texture not multiple of 4

* v1.12.7.0 (Spring Update 2026 Hotfix 1)

---------

Co-authored-by: Joonas Rikkonen <poe.regalis@gmail.com>
Co-authored-by: Evil Factory <36804725+evilfactory@users.noreply.github.com>
Co-authored-by: MapleWheels <njainanan@hotmail.com>
2026-04-25 12:10:24 +08:00
Evil Factory
928cfb4fde Re-add loaded Lua hook call 2026-04-09 12:09:00 -03:00
Evil Factory
a9634988e9 Fix signalReceived hook not being called 2026-04-09 11:18:18 -03:00
MapleWheels
87e0191a97 - Debug tests. 2026-04-09 08:35:17 -04:00
Evil Factory
790378d2a5 Merge remote-tracking branch 'upstream/master' into develop 2026-04-09 09:12:16 -03:00
Regalis11
a4607dffad v1.12.6.2 (Spring Update 2026) 2026-04-09 15:10:07 +03:00
MapleWheels
ef66d27ffe - Fixed network synchro of vars, needs synctype testing. 2026-04-09 05:34:50 -04:00
MapleWheels
84cb7cfeb7 server auth var. 2026-04-09 01:29:48 -04:00
MapleWheels
53be3f0073 Fixed lua attempting to invoke the base interface type. 2026-04-08 19:49:57 -04:00
MapleWheels
89aa818c0b Made Config initialization errors more forgiving to noobs. 2026-04-08 19:38:31 -04:00
Evil Factory
3a5fbfde1e Fix cs enabled for session state not being preserved between reloads 2026-04-08 20:12:20 -03:00
Evil Factory
b9b457262f Test Lua config mod 2026-04-08 19:56:32 -03:00
Evil Factory
e161d92117 Fix settings not being properly exposed to Lua 2026-04-08 19:44:16 -03:00
MapleWheels
2e656509a8 - Added null check to get string value. 2026-04-08 17:53:12 -04:00
MapleWheels
b0b4acf392 - Made readonly readonly. 2026-04-08 17:29:56 -04:00
Evil Factory
e9673bf7f5 Update moonsharp 2026-04-08 18:29:29 -03:00
Evil Factory
eeeb3d9db3 Expose all settings in Lua 2026-04-08 18:29:07 -03:00
Evil Factory
c882b0bb45 Update moonsharp 2026-04-08 18:07:12 -03:00
Evil Factory
6c32873d4e Only register ILuaConfigService 2026-04-08 18:04:25 -03:00
MapleWheels
bdd4dcfb9e Disabled caching on the ConfigService. 2026-04-08 16:27:01 -04:00
MapleWheels
ebe8ec455f LuaCs CSharp Enabled Rework
- New UI for the prompt

- Third time's the charm.

- Fixed TOCTOU for cs prompt on main menu. - Fixed SubEditor not running lua when don't run is selected for cs.

- LuaCs CSharp Enabled Rework
2026-04-08 16:31:42 -03:00
Evil Factory
d440ccbce2 Register IConfigService and add back some missing APIs 2026-04-08 15:32:31 -03:00
MapleWheels
1294ba6dec - Added API to get package by name to PackageManagementService.cs and LuaScriptManagementService.cs
- Added ILuaConfigService to Lua API.
2026-04-08 11:03:52 -04:00
MapleWheels
4c8e016dea - Added extra source code translation (for CTS).
- Added localization to SettingList.cs display dropdown.
2026-04-08 08:09:45 -04:00
MapleWheels
232f7203e2 Fixed shared Csharp src files not being included if there weren't also architecture-specific files. 2026-04-07 16:07:55 -04:00
MapleWheels
df0a4e62f5 Added logging for additional plugin loading exceptions. 2026-04-07 15:43:01 -04:00
MapleWheels
7055480015 - Fixed publicized Barotrauma.dll missing error on DedicatedServer.
- Fixed non-implemented folder search for ModConfig resources.
2026-04-05 13:09:34 -04:00
Evil Factory
0e14983e88 Allow System.Console 2026-04-05 11:20:08 -03:00
Evil Factory
9b05c51ae5 Fix calling Hook.Call directly from Lua being broken 2026-04-04 22:52:27 -03:00
Evil Factory
5e003bcf11 Fix missing Game.Commands 2026-04-04 22:30:46 -03:00
Evil Factory
121f670c8b Fix character.death hook getting called a billion times per second 2026-04-04 13:04:34 -03:00
Evil Factory
f05d4ef129 Fic CSharp enabling message printing before config got loaded 2026-04-04 12:24:12 -03:00
Evil Factory
4167448279 Fix Game.Settings being nil 2026-04-04 11:50:35 -03:00
MapleWheels
413cc3ed4c - Added setting to disable lua scripts caching in the storage service for scenarios that use extern editors. 2026-04-02 23:29:21 -04:00
MapleWheels
4f2da55a8e - Added legacy LuaCsPerformanceCounter.cs
- Added legacy redirects.
- Added IConsoleCommandsService injection to plugins.
2026-03-31 10:48:44 -04:00
Maplewheels
f1808d9231 Added verbose cs compilation. 2026-03-29 00:30:18 -04:00
Evil Factory
bcec409618 Fix deep-fried main menu text 2026-03-28 22:20:22 -03:00
Evil Factory
14c610e6c7 Re-added the install_cl_lua command 2026-03-28 21:50:13 -03:00
Evil Factory
958335a01c Replicate old Add() method signature structure 2026-03-28 14:14:11 -03:00
Evil Factory
b358882016 This should be ItemPrefab, not Item, also add it the source code compat 2026-03-28 14:03:28 -03:00
Evil Factory
b8f1642d9d Simplify command register 2026-03-28 13:04:11 -03:00
Evil Factory
64818775cb Whoops 2026-03-28 13:04:04 -03:00
Evil Factory
5bfa15564a Move partial classes to extension methods, the ones that can't, turn into Lua compatibility members 2026-03-28 12:51:53 -03:00
Evil Factory
2ea97d3d5e Add CallLuaFunction legacy redirect 2026-03-28 10:52:33 -03:00
Evil Factory
bb2490eb13 Fix wrong arguments in RemovePatch 2026-03-27 21:58:06 -03:00
Evil Factory
57daa3ccb7 Fix missing type register 2026-03-27 21:40:44 -03:00
MapleWheels
c1fdedf955 Fixed ConfigService not filtering out configs that it shouldn't be loading. 2026-03-24 10:36:46 -04:00
MapleWheels
b70b6bf326 Fixed deadlock scenario in the event dispatcher CRUD API. 2026-03-24 02:44:08 -04:00
MapleWheels
28acf02de0 Updated client-side/common files tracking list for luacsinstaller. 2026-03-23 08:47:19 -04:00
MapleWheels
1e76377b63 - Added full implementation of lua event "modifyChatMessage" in IEvents. 2026-03-23 07:51:49 -04:00
MapleWheels
3dd9cfa741 Implemented plugin assembly lookup api. 2026-03-23 07:30:15 -04:00
Maplewheels
26ac37ed46 Added fallback native assembly resolver. 2026-03-23 00:25:51 -04:00
Maplewheels
5c136aab41 Added test debug settings for settings list. 2026-03-22 23:56:27 -04:00
Maplewheels
cf6104f630 Added Dropdown menu implementation for settings list. 2026-03-22 23:56:26 -04:00
Maplewheels
7c8dd452cf SettingsList implementation.
Merge conflict resolution.
2026-03-22 23:56:25 -04:00
MapleWheels
cf3a50d6b5 - Added back modifyChatMessage.
- Added missing commit file for previous commit.
2026-03-22 18:05:21 -04:00
MapleWheels
ac329a70a4 - Reduced console spam when failing to load config file in release builds.
- Added console logging for failing to unload assembly load ctx.
- Removed unused StorageService instance.
- Removed unused luacs settings.
- Added plugin event service and event service dispatcher api.
2026-03-22 18:01:19 -04:00
Evil Factory
e62450c763 Actually subscribe to IEventAssemblyUnloading 2026-03-22 17:24:02 -03:00
Evil Factory
44a9ce2417 Guh? 2026-03-22 12:44:17 -03:00
Evil Factory
23e0ff7aa6 Remove types that have been registered when unloading 2026-03-22 12:42:23 -03:00
Evil Factory
de53b4514e Fix console command registration 2026-03-22 12:42:09 -03:00
Maplewheels
994610869d Fixed deadlock scenario caused by unsubscribing during an event. 2026-03-22 01:56:30 -04:00
Evil Factory
b0e3faa2ad Make LuaCsLogger public 2026-03-21 15:59:48 -03:00
MapleWheels
fe34dcb0bd Added null location check to assembly inclusion list. 2026-03-17 10:58:48 -04:00
Evil Factory
8717706b1c Fix check ready to run not working properly 2026-03-15 18:44:15 -03:00
Evil Factory
b402e09b82 Move MainMenu code to a separate service that just injects our stuff into the mainmenu 2026-03-15 17:52:40 -03:00
Maplewheels
80832459b9 Added api for getting content package associatged with a plugin type. 2026-03-14 23:47:44 -04:00
Evil Factory
cf2c8b8e0e Add missing HttpGet in the interface 2026-03-14 14:45:03 -03:00
Evil Factory
f802356b0a Add legacy redirect for LuaCsSetup.Timer 2026-03-14 14:31:03 -03:00
Evil Factory
ecc4d8a6c1 Update moonsharp 2026-03-14 14:00:59 -03:00
Evil Factory
d4b774642b ModUtils namespace compatibility 2026-03-14 13:41:12 -03:00
Evil Factory
103f28481d Register setting types 2026-03-14 12:07:23 -03:00
Evil Factory
dd51bdae3f Revert "performance fixes for IDE/attached debugger stuttering."
This reverts commit e5aa381e38.
2026-03-14 12:04:29 -03:00
MapleWheels
e5aa381e38 performance fixes for IDE/attached debugger stuttering. 2026-03-10 03:22:30 -04:00
Maplewheels
8190b3f312 Fixed cs enabled prompt for hosts. 2026-03-08 22:07:56 -04:00
MapleWheels
e26cfa2e52 [Untested][Save/Sync]
- Fix for IsCsEnabled check.
2026-03-06 18:49:55 -05:00
MapleWheels
5b52d22b1f Added references cache for some hot paths services. 2026-03-06 18:15:44 -05:00
MapleWheels
5a452709c3 Minor adjustment to setting entry height and styles. 2026-03-06 08:31:09 -05:00
MapleWheels
8470e81dc7 Added tooltips and SettingControl input listener to the settings menu. Minor cosmetic adjustments. 2026-03-06 04:57:00 -05:00
MapleWheels
947a481400 Added spacer to settings labels. 2026-03-05 19:43:04 -05:00
MapleWheels
368b18d440 - Fixed shet. 2026-03-05 17:48:27 -05:00
MapleWheels
1fe68aa861 - Added float, double settingentry types. Adjusted display formatting.
- Added menu refresh on Apply button.
- Fixed package name resolution for invalid chars.
- Added helper console command to get xml-safe name for use in localization files.
- Made error messages for bad settings xml more descriptive.
- Modified GUISlider.
- Converted HarmonyEventPatchesService to a System.
- Fixed package name resolution for incompatible names in Xml and files, in many places.
- Fixed base textbox implementation for settings.
2026-03-05 15:45:00 -05:00
MapleWheels
f38a7bd574 The gameplay settings menu kinda works (only for luacsforbarotrauma). 2026-03-04 20:39:13 -05:00
MapleWheels
ce8b984542 Fixed Csharp/Shared path resolution in ModConfigService. 2026-03-04 14:51:31 -05:00
MapleWheels
d0969cc723 - Fixed assembly references not resolving for minor dll signature differences.
- Fixed deadlock during assembly resolution.
2026-03-04 14:38:00 -05:00
MapleWheels
a66b9041ec Fixed reference name error. 2026-03-03 22:44:56 -05:00
MapleWheels
83c198bc26 [Save/Sync] Commit work before .NET 10 tests. 2026-03-03 20:45:51 -05:00
MapleWheels
26b3827210 - Made publicizer only copy the required assemblies. 2026-03-02 08:46:03 -05:00
Maplewheels
4b04131fe3 Added post-publishing publicized assemblies copy task. 2026-03-02 02:45:43 -05:00
MapleWheels
168ce83820 Fixed potential NRE that shouldn't happen so long as the LuaCsForBarotrauma exists. 2026-03-01 17:13:15 -05:00
Evil Factory
5cbb635e54 Ok this should be the last one 2026-03-01 14:39:07 -03:00
Evil Factory
0452c1fc2d oops 2026-03-01 14:18:51 -03:00
Evil Factory
800dec3b84 I guess this doesnt exist anymore 2026-03-01 14:06:45 -03:00
Evil Factory
580f26b526 New CI files 2026-03-01 13:40:08 -03:00
Evil Factory
f8ff97d2b7 Fix debug console commands 2026-03-01 13:26:36 -03:00
Evil Factory
9ee4728e2a Better logging 2026-03-01 13:09:05 -03:00
Evil Factory
22a74bf1fd Move compat hooks 2026-03-01 12:43:59 -03:00
Evil Factory
845fcefad7 Fix Start(0 not returning an empty write only message 2026-03-01 09:50:52 -03:00
Maplewheels
9b55bf4847 [Save/Sync] Work on the settings menu. 2026-03-01 06:03:31 -05:00
Maplewheels
e74f083300 LuaCs package bug fixed. 2026-02-28 22:32:01 -05:00
Maplewheels
09bc2d0891 - Weird LuaCs Settings Menu bug present: package is loaded on startup but is then unloaded if the settingsmenu is opened and the package is not in the enabled list. 2026-02-28 22:10:29 -05:00
Evil Factory
28b355911d Add missing ILuaCsNetworking APIs 2026-02-28 17:57:02 -03:00
Evil Factory
e2c4282477 Add netMessageReceived hook back 2026-02-28 16:56:14 -03:00
Evil Factory
3192cc8b00 Remove need to define custom network header in upstream 2026-02-28 16:47:18 -03:00
Evil Factory
de73a18637 Compatibility for in memory scripts that used GameMain.LuaCs 2026-02-28 16:13:07 -03:00
Evil Factory
8e8b8eb8aa GameMain.LuaCs is no more 2026-02-28 16:05:20 -03:00
MapleWheels
c676233dfd [Save/Sync] Work on SettingsMenu 2026-02-27 17:56:47 -05:00
MapleWheels
9b61cda6cf - SettingsMenu work.
- Fixed NRE on NetSync in SettingEntry<T>
2026-02-27 17:25:25 -05:00
MapleWheels
ebf2935fb4 - Some menu implementation. 2026-02-27 15:21:40 -05:00
MapleWheels
6e556a02d4 I'm cooked, will finish this later. 2026-02-26 19:16:50 -05:00
MapleWheels
94556fd6e7 - SettingsMenuService added.
- Added Settings Tab to vanilla SettingsMenu
- Fixed ISystem implementation.
2026-02-25 15:19:01 -05:00
MapleWheels
d9d980122d - Moved all console commands to their respective services and added via injector service.
- Fixed LuaCs IsCsEnabled prompt not working.
- Add settings profiles support.
2026-02-24 15:26:49 -05:00
MapleWheels
394856fa04 - Final commit before migration to /dev
- Localization files added.
2026-02-23 17:40:59 -05:00
Maplewheels
a28333ff4e Implemented ConfigProfiles internally. 2026-02-23 01:01:23 -05:00
Maplewheels
f4138d2398 - Added networking permission checks to SettingEntry.
- Removed error logging for missing save data for settings.
2026-02-22 06:35:37 -05:00
Evil Factory
91992194a9 Fix return events not working in Lua 2026-02-20 20:40:06 -03:00
Evil Factory
a7e9dc8c9c oops 2026-02-20 20:39:53 -03:00
Evil Factory
da5b9389db Fix missing HookMethod APIs in the interface 2026-02-20 20:20:15 -03:00
Evil Factory
ddd9dac2fb Fix Descriptors not being populated correctly 2026-02-20 19:32:15 -03:00
Evil Factory
124b1e545d Fix missing ILuaCsNetworking API 2026-02-20 19:32:03 -03:00
Evil Factory
f52617deab The great event move 2026-02-20 18:45:41 -03:00
Joonas Rikkonen
c8657caefa Update .NET SDK version from 6 to 8 in README 2026-02-20 19:56:19 +02:00
Maplewheels
c28a08a713 Revert "Oops"
This reverts commit a0287b8561.
2026-02-16 02:22:19 -05:00
Maplewheels
a0287b8561 Oops 2026-02-16 02:22:14 -05:00
Maplewheels
6ac49a10f4 - XML GUI Asset service implemented (alpha, requires testing). 2026-02-16 02:22:14 -05:00
Evil Factory
f70251fa3b Event pain 2026-02-15 17:58:25 -03:00
Evil Factory
13bfffa777 More events moved 2026-02-15 16:57:16 -03:00
Evil Factory
a50dce8fc2 Forgot to remove old Hook Call 2026-02-13 19:56:33 -03:00
Evil Factory
a36bd70505 Strip down the original logger class 2026-02-13 18:58:35 -03:00
Evil Factory
36471f2239 Move more events to be Harmony patches 2026-02-13 18:44:27 -03:00
MapleWheels
dff38f7609 - Added XmlDoc for InternalScript instance. 2026-02-12 20:21:46 -05:00
Evil Factory
5f38b4a43a Fix bullshit Lua issues 2026-02-12 21:50:13 -03:00
MapleWheels
07d838eee0 Finished removing Impromptu Interfaces package. 2026-02-12 14:59:24 -05:00
MapleWheels
5747d896eb - Removed ImpromptuInterfaces 2026-02-12 14:53:33 -05:00
MapleWheels
ad152ee747 - pOoosh 2026-02-11 15:19:40 -05:00
MapleWheels
471e256353 Revert "-- MonoMod.Hook replacement."
This reverts commit 948b7c48c5.
2026-02-11 15:17:32 -05:00
MapleWheels
59584fefc1 - Updated dependencies versions.
- Made EventService handle errors again.
2026-02-11 15:16:04 -05:00
MapleWheels
948b7c48c5 -- MonoMod.Hook replacement. 2026-02-11 12:57:49 -05:00
MapleWheels
5e33a908d2 Removed NIException. 2026-02-10 19:15:23 -05:00
MapleWheels
9e2fc13460 - Fixed the server not loading saved convars and not having absolute authority. 2026-02-10 18:48:31 -05:00
Evil Factory
6bbe5be5e6 Fix networking being completely bamboozled 2026-02-10 20:26:46 -03:00
MapleWheels
9dde5cac7d Fixed Evil's obsession with OnKeyUpdate. 2026-02-10 15:11:46 -05:00
MapleWheels
6b9e48f96a - Added ISystem (automatically run services) service type. 2026-02-10 14:28:22 -05:00
MapleWheels
30149b504d - Added publicized assemblies to LuaCsForBarotrauma package via ModConfig.xml
- Added XmlAttribute tags for ModConfig.xml defined properties.
- GetType and GetImplementingTypes<T> rework.
2026-02-09 21:32:57 -05:00
Evil Factory
fb4648d759 Converting everything into Harmony patches part 1 2026-02-09 21:54:44 -03:00
Evil Factory
d14c353115 Remove old LuaCsNetworking 2026-02-09 21:01:07 -03:00
MapleWheels
f1bae4c1ca - Added Copy-On-Build for publicized assemblies to the luatrauma localmods folder. 2026-02-09 18:37:27 -05:00
Evil Factory
a61e705dbf Better LoggerService 2026-02-09 19:51:31 -03:00
MapleWheels
64831bd580 - Made the assembly compilation logs output in production builds.
- Removed double logging in debug builds.
2026-02-09 17:43:59 -05:00
MapleWheels
dc1093eeed Added LuaCs package lookup to the info provider. 2026-02-09 17:18:50 -05:00
MapleWheels
95376622fa - Added LuaCsForBarotrauma check to enabled packages list. 2026-02-09 16:58:53 -05:00
MapleWheels
511f98ec18 - Added pre-touch to the ContentPath.FullPath to make them thread-safe. 2026-02-09 15:52:37 -05:00
Evil Factory
70e98467e8 nuh uh 2026-02-08 23:47:02 -03:00
Evil Factory
e8bec96970 THE REQUIRE PATHS WORK!@!!!!!!11! 2026-02-08 23:38:52 -03:00
Maplewheels
a505d48a4c - Loading/Saving for Settings via console. 2026-02-08 20:23:49 -05:00
Evil Factory
bcc4357a16 Pass in cs enabled check in ExecuteLoadedScripts 2026-02-08 21:46:00 -03:00
Evil Factory
668197ad6f Fix Lua/Cs execute order 2026-02-08 21:45:17 -03:00
Evil Factory
0a91b89694 Add publicized assemblies to metadata references 2026-02-08 21:29:31 -03:00
Evil Factory
5b10661874 Legacy network stuff 2026-02-08 18:26:59 -03:00
Evil Factory
36a126774a Register INetworkIdProvider 2026-02-08 15:06:22 -03:00
Evil Factory
705137e993 Finalize networking service 2026-02-08 15:03:03 -03:00
Evil Factory
ce4cd1fefd Implement most of the net var networking functionality 2026-02-08 11:51:09 -03:00
Maplewheels
224e32ccf1 Some work on save/load for configs. 2026-02-08 06:39:44 -05:00
Evil Factory
f01ee61278 Clear file cache when resetting LuaScriptManagementService 2026-02-08 01:37:24 -03:00
Evil Factory
c637e34b48 Fix GUI enums in Lua 2026-02-08 01:30:44 -03:00
Maplewheels
67c195034d Network Sync work. Added network unique key. 2026-02-07 22:58:56 -05:00
Evil Factory
02b1f524b6 Re-enable DefaultHook.lua 2026-02-07 23:29:22 -03:00
Evil Factory
e76aaf5a34 Fix deadlock when reloading packages 2026-02-07 23:29:00 -03:00
Evil Factory
422e8a6185 Misc Lua fixes 2026-02-07 20:11:46 -05:00
Evil Factory
ba10d9d031 Working NetworkingService without net vars 2026-02-07 20:11:46 -05:00
Maplewheels
87dc9be10e -Changed NetworkSync interfaces. 2026-02-07 20:11:46 -05:00
Evil Factory
d47b75c778 Give Lua references to IEvent and finally add an alias for think 2026-02-07 20:11:46 -05:00
MapleWheels
2c29969bfb - Oops. 2026-02-07 20:11:46 -05:00
Evil Factory
c84d9660e2 Add command cfg_getvalue 2026-02-07 20:11:46 -05:00
MapleWheels
dcd7df4860 Fixed TrySetValue returning an incorrect result. 2026-02-07 20:11:46 -05:00
Evil Factory
b3d0fbeb5d Add cfg_setvalue command 2026-02-07 20:11:46 -05:00
MapleWheels
fa340e91de Fixed an issue affecting parsing the required runstate to edit a setting. 2026-02-07 20:11:46 -05:00
MapleWheels
771e73a798 - Basic Config & Settings working (read-only, changes must be made via debug halt). 2026-02-07 20:11:46 -05:00
MapleWheels
e75208507d - Config Services almost ready.
- Refactored and flattened namespaces.
2026-02-07 20:11:46 -05:00
MapleWheels
863ee23583 - Some work on config service. 2026-02-07 20:11:46 -05:00
MapleWheels
9cc20a03c0 Fixed networking references errors. 2026-02-07 20:11:46 -05:00
MapleWheels
cae3741953 Made most of the networking interfaces public. 2026-02-07 20:11:46 -05:00
Evil Factory
80555ef933 IT WORKS!!!!!!!!!!!!!!!!!!!! 2026-02-07 20:11:45 -05:00
Evil Factory
cf251451ed Fix EventService.Call not implemented correctly 2026-02-07 20:11:45 -05:00
MapleWheels
4cf4b1604b Fixed some NREs. 2026-02-07 20:11:45 -05:00
MapleWheels
fd037153ee - Fixed recursion deadlock due to the EventService.Reset() being called during event publishing. 2026-02-07 20:11:45 -05:00
MapleWheels
02a7338ab8 Removed duplicate rawrrs 2026-02-07 20:11:45 -05:00
Evil Factory
70dd602bcf Move the Lua IL patching bullshit to a separate service 2026-02-07 20:11:45 -05:00
MapleWheels
ea602f6d2f Woof 2026-02-07 20:11:45 -05:00
MapleWheels
06348d3ba5 It works. Except (HookMethod->Harmony: L189) is throwing NRE. 2026-02-07 20:11:45 -05:00
MapleWheels
2eb593f461 - Debugging LuaCsHook compat issues. 2026-02-07 20:11:45 -05:00
MapleWheels
244c0fbec3 Made ACsMod even more useless so hopefully people stop using it... 2026-02-07 20:11:45 -05:00
MapleWheels
7b529bce57 Revert "- Removed ACsMod.cs"
This reverts commit 54a3e2e5de0cff38ea4fc3750d2eb90f5ea73fe9.
2026-02-07 20:11:45 -05:00
MapleWheels
36bed09bde - Fixed stack ovewrflow from ServicesProvider (???). 2026-02-07 20:11:45 -05:00
Maplewheels
024b07d5f4 Added logging to the EventService. 2026-02-07 20:11:45 -05:00
Maplewheels
0cab7954f8 Fixed immediate errors (untested). 2026-02-07 20:11:45 -05:00
Maplewheels
b325a01eea Rewrote the EventService. 2026-02-07 20:11:45 -05:00
MapleWheels
7e541cef3d - Removed ACsMod.cs 2026-02-07 20:11:45 -05:00
MapleWheels
bb8869268e - Refactored the EventService interfaces and event system, incomplete. 2026-02-07 20:11:45 -05:00
Maplewheels
cb171d350d Alpha PluginManagementService, plugin loading functionality implemented. 2026-02-07 20:11:45 -05:00
Evil Factory
5777b64a18 Move LuaUserData and registration into a proper service 2026-02-07 20:11:41 -05:00
Maplewheels
9b9529107c Added limited multithreaded compatibility. Still requires locks. 2026-02-07 20:11:33 -05:00
MapleWheels
5421c7df4f - Made SyncPackages function always complete the unload->reload process.
- Basic assembly loading is completed (alpha), unloading/disposal not yet supported.
2026-02-07 20:11:33 -05:00
Evil Factory
4f02cb4967 Working Hook.Patch and old patch methods 2026-02-07 20:11:33 -05:00
Evil Factory
6b8a0a7dca Take in account ForcedAutorun for legacy as well 2026-02-07 20:11:32 -05:00
Evil Factory
dfb31eef16 Move Lua classes to the appropriate places 2026-02-07 20:11:16 -05:00
Evil Factory
c6c0aadb00 Rename LuaCsHook 2026-02-07 20:11:08 -05:00
Evil Factory
3b65ea9008 Remove unused LuaCsConfig 2026-02-07 20:11:08 -05:00
Evil Factory
708fe93efe Some extra logging and bring back LuaCsTimer 2026-02-07 20:11:08 -05:00
MapleWheels
13a9bc443e - Some work on PluginManagementService, IAssemblyLoaderService and IAssemblyManagementService refactors.
- Fixed mod list sync not checking for zero package diff length.
- Fixed the services provider not being able to inject itself as a dependcency.
- Added UseInternalName data spec to ModConfig.xml
- Changed Basic.Reference.Assemblies to Net80.
2026-02-07 20:11:08 -05:00
MapleWheels
37e3a195dc - Now logs results from SyncLoadedPackagesList 2026-02-07 20:11:08 -05:00
MapleWheels
a28a6f3320 - Added LuaCs ordering filter. 2026-02-07 20:11:07 -05:00
Evil Factory
67d3d5f587 Reimplementation of DoString with lua/cl_lua commands and fix Lua scripts not being loaded properly 2026-02-07 20:11:07 -05:00
Evil Factory
f0f09c20fa Plugin moment 2026-02-07 20:11:07 -05:00
Evil Factory
f28749d455 Missing IsAutorun 2026-02-07 20:11:07 -05:00
Evil Factory
ab2638b2cb Fix event service not clearing _luaOrphanSubscribers 2026-02-07 20:11:07 -05:00
MapleWheels
22f587b7b9 Changed ModConfig.xml spec: RunFile => IsAutorun. 2026-02-07 20:11:07 -05:00
MapleWheels
92232d114b Ensures that ILuaCsHook will resolve to the existing event service instance. 2026-02-07 20:11:07 -05:00
Evil Factory
6619def365 The concurrent toolbox dictionary situation is crazy 2026-02-07 20:11:07 -05:00
MapleWheels
c776a5424d Made GetType return interfaces by default. 2026-02-07 20:11:07 -05:00
MapleWheels
2a1d7760e6 - Enabled caching for LuaScriptLoader.cs 2026-02-07 20:11:07 -05:00
MapleWheels
009957e3b6 - Added alpha legacy mod support.
- StorageService: Fixed ContentPackages' directories not properly resolving.
- TODO: Rewrite StorageService LocalData functions to use ContentPath for resolution.
2026-02-07 20:11:07 -05:00
Evil Factory
297f6a38cb Basic legacy Lua converter 2026-02-07 20:11:07 -05:00
MapleWheels
440cbed76a - Fixed find files in package using the wrong ContentPackage property. 2026-02-07 20:11:07 -05:00
MapleWheels
b36224f480 Removed unused data interfaces. 2026-02-07 20:11:06 -05:00
MapleWheels
f9a467453a - Removed all MoveImmute situations.
- Added filtering and ordering functions for package resources.
2026-02-07 20:11:06 -05:00
Maplewheels
60ed549605 - Removed unused scheduler context. 2026-02-07 20:11:06 -05:00
Evil Factory
3d51abc56b Semi-working Lua scripts 2026-02-07 20:11:06 -05:00
Maplewheels
295c365a8f - The return null situation is no longer crazy. 2026-02-07 20:11:06 -05:00
Maplewheels
adf9303a7e - The GetType lag situation is less crazy. 2026-02-07 20:11:06 -05:00
Maplewheels
b626fd1e47 - The GetType lag situation is craazy. 2026-02-07 20:11:06 -05:00
Maplewheels
ed09908c3b - Added default load context type resolution. 2026-02-07 20:11:06 -05:00
Maplewheels
0a9c673753 - Removed unused settings
- Added Settings.xml
2026-02-07 20:11:06 -05:00
Maplewheels
c2aa7dd948 Added packages to running packages list on execution. 2026-02-07 20:11:05 -05:00
Evil Factory
25081466be Add the content package and move the Lua files to it 2026-02-07 20:11:02 -05:00
Evil Factory
8dedc54468 Add Any for Platform and Target enums 2026-02-07 20:10:55 -05:00
Maplewheels
d0f5cb87e7 - Fixed resource data not returning into the correct context. 2026-02-07 20:10:55 -05:00
Evil Factory
8dc58445f9 MoveToImmutable -> ToImmutable 2026-02-07 20:10:55 -05:00
Evil Factory
6faf1a0fcb Fix perfidious task moment 2026-02-07 20:10:55 -05:00
Evil Factory
ee0988588f Add ModConfig for test mod 2026-02-07 20:10:55 -05:00
MapleWheels
6e66a3114a - Made Package loading conditional on resources being available.
- Made States registration use named parameters.
- Changed IPluginManagementService interface to better suit expected return results.
2026-02-07 20:10:55 -05:00
MapleWheels
15e0a2bd10 Disabled LuaCsConfig until ModConfig.xml and ISettingEntry<T> are completed. 2026-02-07 20:10:55 -05:00
MapleWheels
09faa8403a Fixed StatemMachine State delegate assignment being backwards. 2026-02-07 20:10:55 -05:00
MapleWheels
7e0e671539 - ConfigService.cs alpha testing. 2026-02-07 20:10:54 -05:00
MapleWheels
cc07db941f - Fixed the "deadlock" (tasks not started). 2026-02-07 20:10:54 -05:00
Evil Factory
f1e1b9238d The deadlock situation is craaaaazy
Fix
2026-02-07 20:10:54 -05:00
MapleWheels
dda26df250 For sure this time....right guys? 2026-02-07 20:10:54 -05:00
MapleWheels
6e7b7c804c - Added other package locations to if statement check. 2026-02-07 20:10:54 -05:00
Maplewheels
6a21255a38 - IT BUILDS!!2:BARO BOOGALOO 2026-02-07 20:10:54 -05:00
MapleWheels
6362a9c34f - Work on LuaCs system state machine. 2026-02-07 20:10:54 -05:00
MapleWheels
3ddaceb5ac - SafeStorageService glow up.
- ILuaScriptLoader now inherits the ISafeStorageValidation interface.
 - LuaScriptLoader now uses the SafeStorageService.
2026-02-07 20:10:54 -05:00
MapleWheels
055a508901 - Deleted unused service IPackageListRetrievalService.cs
- Added caching function to LuaScriptLoader.cs
- Added sample async code to LuaScriptManagementService.cs
- Removed most of the State functions in LuaCsSetup.cs (requires rewrite).
- Fixed CsEnabled check.
- Moved IsRunningWorkshop check to client-only project.
2026-02-07 20:10:54 -05:00
MapleWheels
0bfceacaf3 - Completed most of PackageManagementService.cs
- Some areas of code need to be rewritten for the simplified loading and execution process.
2026-02-07 20:10:54 -05:00
Maplewheels
0438d3c4ba [Save/Sync]
- Work on PMS.
2026-02-07 20:10:54 -05:00
MapleWheels
3e81e27160 [Save/Sync] In-Progress ModConfigXml loading rewrite.
+ Fixed async operations lock for Dispose() pattern in working files.
+ Rewrote StorageService.cs:
--- Now uses ContentPath instead of raw strings where possible.
--- Now throws exceptions for developer errors and critical program states.
+ Rewrote ModConfigService.cs:
--- All functions are now completely async.
+ Removed ConfigProfilesResources completely as they exist in common Config xml files.
+ Somewhat simplified package data and processes.
2026-02-07 20:10:54 -05:00
MapleWheels
42acb32c69 Marked Async-compatibility issue with Log() in LoggerService.cs 2026-02-07 20:10:53 -05:00
Maplewheels
d6968f4ea9 [Save/Sync] Work on ModConfig loading. 2026-02-07 20:10:53 -05:00
Evil Factory
75da3a398d This should be full path 2026-02-07 20:10:53 -05:00
Evil Factory
1dad2babb7 Move Lua compatibility files around and start adding them to LuaScriptManagementService 2026-02-07 20:10:53 -05:00
Evil Factory
fe982b15b0 Fix LuaScriptManagementService compile error 2026-02-07 20:10:53 -05:00
Maplewheels
595470ccfb [Save/Sync] In-Progress rewrite of ConfigService and ModConfigService 2026-02-07 20:10:53 -05:00
MapleWheels
7d39c092c6 [Save/Sync] Big If tru Rewrite in progress.
- Removed IProcessors
- Removed old ModConfigService format.
- Converting to ContentPath from absolute paths where possible.
- Added: Microsoft.Toolkit.Diagnostics package.
2026-02-07 20:10:53 -05:00
MapleWheels
6a6be3caa4 - Removed all package dependency lookup code.
- Changed from absolute file paths to the upstream `ContentPath` system.
2026-02-07 20:10:53 -05:00
Evil Factory
1daf68dea1 Oops 2026-02-07 20:10:53 -05:00
Evil Factory
4f8531332e Implement LogResults 2026-02-07 20:10:53 -05:00
Evil Factory
d70885711b Fix some runstate if checks 2026-02-07 20:10:53 -05:00
Evil Factory
6499e7608c Fix package management service constructor and create [DebugOnlyTest]TestLuaMod 2026-02-07 20:10:53 -05:00
MapleWheels
569e14f50f - Removed generic exception from LuaCsSetup.cs services retrival, replaced with exception propagation.
- Fixed `PerScopeLifetime` and `Transient` lifetimes for container services.
2026-02-07 20:10:53 -05:00
Evil Factory
71c2e54afd Remove CheckUpdate 2026-02-07 20:10:53 -05:00
Maplewheels
bd5d04f5ab Work on plugin loader. 2026-02-07 20:10:53 -05:00
Maplewheels
26b657a96f [In-Progress] Plugin system rewrite.
Game starts up, runs until unimplemented functions are reached without errors.
2026-02-07 20:10:52 -05:00
Maplewheels
cce5bf26c8 Completed SafeStorageService.cs 2026-02-07 20:10:52 -05:00
Evil Factory
4d97a427f9 WIP Lua script management service 2026-02-07 20:10:52 -05:00
MapleWheels
aa7e825e70 - Deleted old assembly loader. 2026-02-07 20:10:52 -05:00
MapleWheels
2778df0fe7 - Changes to the Lua ScriptSystem spec. 2026-02-07 20:10:52 -05:00
MapleWheels
c6713f37bb IT BUILDS!!!
- Removed LocalizationServices and other sus things.
- Rewrote AssemblyLoader
[In Progress] SafeStorageService
[In Progress] LuaScriptLoader
2026-02-07 20:10:52 -05:00
MapleWheels
52d920d969 [Milestone] PackageManagementService completed.
- ContentPackageInfoLookup Service completed.
- Implemented ModConfigService.cs
- Implemented some of the resource processors.
2026-02-07 20:10:45 -05:00
EvilFactory
cb88d215fa LuaGame legacy service 2026-02-07 20:10:39 -05:00
MapleWheels
7436ea3e8c - Finished most of LuaCsSetup top-level functionality.
- Removed some unneeded interface definitions.
- Clean-slated some Services that need to be re-written.
2026-02-07 20:10:39 -05:00
MapleWheels
d2b9ca4c1b [Refactor-Minor]
- Refactored interface definition.
- Plugin Loading System Refactor (incomplete).
2026-02-07 20:10:39 -05:00
MapleWheels
4b2bac7cd8 [Milestone] StorageService completed. 2026-02-07 20:10:39 -05:00
Regalis11
1da82cdec2 v1.7.7.0 (Winter Update 2024) 2026-02-07 20:10:39 -05:00
MapleWheels
76fc52e042 - Work on storage service. Pre-squash commit. 2026-02-07 20:10:39 -05:00
MapleWheels
6880e5e9ee [Milestone] AssemblyLoader completed.
Details:
- Assembly Mgmt Service for loading now a separate interface, not intended for normal use.
- Assembly Loader work; implemented custom dictionary key and table.
- Assembly loading work.
- EventService completed.
- Moved assembly extensions to ModUtils.cs
- Work to event service.
NetworkService work
- Added ImpromptuInterfaces package.
- Networking Service work to support NetVars
- Event Service
- Added assemblies references package for script compilation. Updated Roslyn version for compatibility.
- Package Loading work.
Swap Harmony to HarmonyX
- More refactor conversion to FluentResults.
- Updated StylesService to return Results.
- Refactor of PackageService partially complete.
- Made IService.Reset() required to return a Result.
- Moved plugin/assembly related code to their own folder (same namespace).
- Updated interfaces to reflect the use of Result<T>.
- Partial refactor, incomplete.
- Added 'FluentResults' so we can stop using cursed Exception-based flow control in loading code.
- Added 'OneOf' nuget package: https://github.com/mcintyre321/OneOf
for the implementation of the Optional<T> pattern and complex discrete return types instead of cursed enums (see current AssemblyManager.cs).
- Reapplied old branch changes.
2026-02-07 20:10:26 -05:00
MapleWheels
01cc1d331b -- Squash:
- In progress implementation of services model.
2026-02-07 20:10:04 -05:00
Evil Factory
9e957a75b0 Update MoonSharp 2026-02-02 21:30:06 -03:00
Evil Factory
a546615f69 Oops accidentally broke this check 2026-02-01 09:18:11 -03:00
Evil Factory
9f1c3fa823 Move UserData checks out of Lua 2026-01-31 17:44:36 -03:00
Evil Factory
886eebdbb2 Fix memory leak that happens when you press retry in singleplayer 2025-12-23 20:47:40 -03:00
92 changed files with 743 additions and 1516 deletions

View File

@@ -73,7 +73,7 @@ body:
label: Version
description: Which version of the game did the bug happen in? You can see the current version number in the bottom left corner of your screen in the main menu.
options:
- v1.13.3.1 (Summer Update 2026)
- v1.12.6.2 (Spring Update 2026)
- Other
validations:
required: true

View File

@@ -258,9 +258,7 @@ namespace Barotrauma
//RelativeSpacing = 0.05f
};
GUIFrame left = new(new RectTransform(new Vector2(0.25f, 1f), characterIndicatorArea.RectTransform), style: null);
InventorySlotContainer = new GUICustomComponent(new RectTransform(Vector2.One, left.RectTransform),
InventorySlotContainer = new GUICustomComponent(new RectTransform(new Vector2(0.1f, 1.0f), characterIndicatorArea.RectTransform, Anchor.TopLeft, Pivot.TopRight),
(spriteBatch, component) =>
{
for (int i = 0; i < character.Inventory.Capacity; i++)
@@ -268,10 +266,6 @@ namespace Barotrauma
if (character.Inventory.SlotTypes[i] != InvSlotType.HealthInterface) { continue; }
if (character.Inventory.HideSlot(i)) { continue; }
int width = Character.Inventory.visualSlots[i].Rect.Width;
left.RectTransform.MinSize = new Point(width, left.RectTransform.MinSize.Y);
if (afflictionIconList != null) { afflictionIconList.RectTransform.MinSize = new Point(width, afflictionIconList.RectTransform.MinSize.Y); }
//don't draw the item if it's being dragged out of the slot
bool drawItem = !Inventory.DraggingItems.Any() || !Character.Inventory.GetItemsAt(i).All(it => Inventory.DraggingItems.Contains(it)) || character.Inventory.visualSlots[i].MouseOn();
@@ -298,7 +292,8 @@ namespace Barotrauma
}
});
cprButton = new GUIButton(new RectTransform(new Vector2(0.75f), left.RectTransform, Anchor.BottomLeft, scaleBasis: ScaleBasis.Smallest), text: "", style: "CPRButton")
cprButton = new GUIButton(new RectTransform(new Vector2(0.17f, 0.17f), characterIndicatorArea.RectTransform, Anchor.BottomLeft, scaleBasis: ScaleBasis.Smallest), text: "", style: "CPRButton")
{
UserData = UIHighlightAction.ElementId.CPRButton,
OnClicked = (button, userData) =>
@@ -321,11 +316,12 @@ namespace Barotrauma
return true;
},
ToolTip = TextManager.Get("tutorial.roles.medic.objective.cpr"),
ToolTip = TextManager.Get("doctor.cprobjective"),
IgnoreLayoutGroups = true,
Visible = false
};
var limbSelection = new GUICustomComponent(new RectTransform(new Vector2(0.5f, 1.0f), characterIndicatorArea.RectTransform),
var limbSelection = new GUICustomComponent(new RectTransform(new Vector2(0.4f, 1.0f), characterIndicatorArea.RectTransform),
(spriteBatch, component) =>
{
DrawHealthWindow(spriteBatch, component.RectTransform.Rect, true);
@@ -372,6 +368,8 @@ namespace Barotrauma
CanBeFocused = false
};
characterIndicatorArea.Recalculate();
healthBarHolder = new GUIFrame(new RectTransform(Point.Zero, GUI.Canvas), style: null)
{
HoverCursor = CursorState.Hand

View File

@@ -866,20 +866,7 @@ namespace Barotrauma
GameSettings.SaveCurrentConfig();
});
}, isCheat: false));
commands.Add(new Command("togglespoofeventmanagerid", "togglespoofeventmanagerid: Forces the client to report the last received event ID as always being 1, making the server believe the client is always behind.", (string[] args) =>
{
if (GameMain.Client != null)
{
GameMain.Client.SpoofEntityManagerReceivedId = !GameMain.Client.SpoofEntityManagerReceivedId;
DebugConsole.NewMessage(GameMain.Client.SpoofEntityManagerReceivedId ? "Spoofing enabled ": "Spoofing disabled", Color.Green);
}
else
{
DebugConsole.NewMessage("Not connected to server", Color.Red);
}
}));
commands.Add(new Command("togglegrid", "Toggle visual snap grid in sub editor.", (string[] args) =>
{
SubEditorScreen.ShouldDrawGrid = !SubEditorScreen.ShouldDrawGrid;
@@ -4467,24 +4454,17 @@ namespace Barotrauma
public static void StartLocalMPSession(int numClients = 2)
{
string extraArguments = "-multiclienttestmode";
if (NetConfig.UseLenientHandshake)
{
extraArguments += " -lenienthandshake";
}
try
{
if (Process.GetProcessesByName("DedicatedServer").Length == 0)
{
#if WINDOWS
Process.Start("DedicatedServer.exe", arguments: extraArguments);
Process.Start("DedicatedServer.exe", arguments: "-multiclienttestmode");
#else
Process.Start("./DedicatedServer", arguments: extraArguments);
Process.Start("./DedicatedServer", arguments: "-multiclienttestmode");
#endif
System.Threading.Thread.Sleep(1000);
}
#if DEBUG
GameClient.MultiClientTestMode = true;
#endif
@@ -4498,13 +4478,10 @@ namespace Barotrauma
for (int i = 2; i <= numClients; i++)
{
System.Threading.Thread.Sleep(1000);
string clientArguments = $"-connect server localhost -username client{i} -skipintro";
#if WINDOWS
Process.Start("Barotrauma.exe", arguments: $"{clientArguments} {extraArguments}");
Process.Start("Barotrauma.exe", arguments: "-connect server localhost -username client" + i + " -multiclienttestmode");
#else
Process.Start("./Barotrauma", arguments: $"{clientArguments} {extraArguments}");
Process.Start("./Barotrauma", arguments: "-connect server localhost -username client" + i + " -multiclienttestmode");
#endif
}
}

View File

@@ -14,7 +14,6 @@ using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
namespace Barotrauma
{
@@ -650,12 +649,12 @@ namespace Barotrauma
DrawMessages(spriteBatch, cam);
if (MouseOn != null)
{
MouseOn.OnDrawToolTip?.Invoke(MouseOn);
{
if (!MouseOn.ToolTip.IsNullOrWhiteSpace())
{
MouseOn.DrawToolTip(spriteBatch);
}
MouseOn.OnDrawToolTip?.Invoke(MouseOn);
}
if (SubEditorScreen.IsSubEditor())
@@ -2324,10 +2323,10 @@ namespace Barotrauma
/// <summary>
/// Creates a 7-segment display.
/// </summary>
/// <param name="leftLabel">Returns <see langword="null"/> if <paramref name="leftLabelText"/> is <see langword="null"/>.</param>
/// <param name="leftLabel">Returns <see langword="null"/> if <paramref name="leftLabelText"/> is <see langword="null"/> or empty.</param>
/// <param name="rightLabelText">Defaults to <c>TextManager.Get("kilowatt")</c>.</param>
/// <param name="leftLabelFont">Defaults to <see cref="GUIStyle.LargeFont"/>.</param>
public static GUITextBlock CreateDigitalDisplay(RectTransform rect, [NotNullIfNotNull(nameof(leftLabelText))] out GUITextBlock? leftLabel, out GUITextBlock rightLabel, LocalizedString? leftLabelText = null, LocalizedString? rightLabelText = null, LocalizedString? tooltip = null, GUIFont? leftLabelFont = null)
public static GUITextBlock CreateDigitalDisplay(RectTransform rect, out GUITextBlock? leftLabel, out GUITextBlock rightLabel, LocalizedString? leftLabelText = null, LocalizedString? rightLabelText = null, LocalizedString? tooltip = null, GUIFont? leftLabelFont = null)
{
GUILayoutGroup textArea = new(rect, isHorizontal: true, childAnchor: Anchor.CenterLeft)
{
@@ -2338,7 +2337,7 @@ namespace Barotrauma
};
leftLabel = null;
if (leftLabelText != null)
if (!leftLabelText.IsNullOrEmpty())
{
leftLabel = new GUITextBlock(new RectTransform(new Vector2(0.4f, 1f), textArea.RectTransform), leftLabelText, textColor: GUIStyle.TextColorBright, font: leftLabelFont ?? GUIStyle.LargeFont, textAlignment: Alignment.CenterRight);
}

View File

@@ -384,14 +384,14 @@ namespace Barotrauma
CaretIndex = forcedCaretIndex == - 1 ? textBlock.GetCaretIndexFromScreenPos(PlayerInput.MousePosition) : forcedCaretIndex;
CalculateCaretPos();
ClearSelection();
bool wasSelected = selected;
selected = true;
GUI.KeyboardDispatcher.Subscriber = this;
OnSelected?.Invoke(this, Keys.None);
if (!selected && PlaySoundOnSelect && !ignoreSelectSound)
if (!wasSelected && PlaySoundOnSelect && !ignoreSelectSound)
{
SoundPlayer.PlayUISound(GUISoundType.Select);
}
selected = true;
//set this after we've set selected to true -> otherwise the textbox taking keyboard focus will trigger Select again
GUI.KeyboardDispatcher.Subscriber = this;
}
public void Deselect()

View File

@@ -113,10 +113,7 @@ namespace Barotrauma
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, samplerState: GUI.SamplerState);
if (currentBackgroundTexture.Texture != null)
{
GUI.DrawBackgroundSprite(spriteBatch, currentBackgroundTexture, Color.White, drawArea);
}
GUI.DrawBackgroundSprite(spriteBatch, currentBackgroundTexture, Color.White, drawArea);
overlay.Draw(spriteBatch, Vector2.Zero, scale: overlayScale);
double noiseT = Timing.TotalTime * 0.02f;

View File

@@ -278,12 +278,6 @@ namespace Barotrauma
GameClient.MultiClientTestMode = true;
}
#endif
if (ConsoleArguments.Contains("-lenienthandshake"))
{
NetConfig.UseLenientHandshake = true;
}
GUI.KeyboardDispatcher = new EventInput.KeyboardDispatcher(Window);
PerformanceCounter = new PerformanceCounter();

View File

@@ -127,6 +127,12 @@ namespace Barotrauma
(int)SlotPositions[i].X,
(int)SlotPositions[i].Y,
(int)(slotSprite.size.X * multiplier), (int)(slotSprite.size.Y * multiplier));
if (SlotTypes[i] == InvSlotType.HealthInterface &&
character.CharacterHealth?.InventorySlotContainer != null)
{
slotRect.Width = slotRect.Height = (int)(character.CharacterHealth.InventorySlotContainer.Rect.Width * 1.2f);
}
ItemContainer itemContainer = slots[i].FirstOrDefault()?.GetComponent<ItemContainer>();
if (itemContainer != null)
@@ -616,7 +622,6 @@ namespace Barotrauma
for (int i = 0; i < capacity; i++)
{
if (HideSlot(i)) { continue; }
var item = slots[i].FirstOrDefault();
if (item != null)
{

View File

@@ -26,7 +26,7 @@ namespace Barotrauma.Items.Components
}
else
{
DisplayMsg = TextManager.ParseInputTypes(TextManager.Get(Msg)).Fallback(Msg);
DisplayMsg = TextManager.ParseInputTypes(TextManager.Get(Msg));
}
CharacterHUD.RecreateHudTextsIfControlling(Character.Controlled);

View File

@@ -12,12 +12,9 @@ namespace Barotrauma.Items.Components
private partial class PowerGroup
{
private GUIFrame? frame;
private GUIFrame? groupContent;
private GUILayoutGroup? nameGroup;
private GUITextBox? nameBox;
private GUITextBlock? loadDisplayNameLabel;
private GUIScrollBar? ratioSlider;
private readonly List<GUITextBlock> powerUnitLabels = [];
private readonly List<GUITextBlock> powerUnitLabels = new List<GUITextBlock>();
private GUIFrame? divider;
public bool IsVisible { get; private set; } = true;
@@ -25,9 +22,9 @@ namespace Barotrauma.Items.Components
public void CreateGUI()
{
frame = new GUIFrame(new RectTransform(new Vector2(1f, 0.25f), distributor.groupList!.Content.RectTransform, minSize: (0, 130)), style: null);
groupContent = new GUIFrame(new RectTransform(frame.Rect.Size - new Point(10), frame.RectTransform, Anchor.Center), style: null);
GUIFrame groupContent = new(new RectTransform(frame.Rect.Size - new Point(10), frame.RectTransform, Anchor.Center), style: null);
nameGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.65f, 0.33f), groupContent.RectTransform, Anchor.TopLeft), isHorizontal: true, childAnchor: Anchor.CenterLeft)
GUILayoutGroup nameGroup = new(new RectTransform(new Vector2(0.65f, 0.33f), groupContent.RectTransform, Anchor.TopLeft), isHorizontal: true, childAnchor: Anchor.CenterLeft)
{
Stretch = true
};
@@ -40,7 +37,7 @@ namespace Barotrauma.Items.Components
return true;
}
};
nameBox = new GUITextBox(new RectTransform(Vector2.One, nameGroup.RectTransform), Screen.Selected == GameMain.SubEditorScreen ? Name : DisplayName.Value, font: GUIStyle.SubHeadingFont, style: "GUITextBoxNoStyle")
nameBox = new GUITextBox(new RectTransform(Vector2.One, nameGroup.RectTransform), Name, font: GUIStyle.SubHeadingFont, style: "GUITextBoxNoStyle")
{
MaxTextLength = MaxNameLength,
OverflowClip = true,
@@ -51,40 +48,17 @@ namespace Barotrauma.Items.Components
return true;
}
};
nameBox.OnSelected += (tb, _) =>
{
if (tb.Selected) { return; }
tb.Text = Name;
};
nameBox.OnDeselected += (tb, _) =>
{
Name = tb.Text;
tb.CaretIndex = 0;
if (GameMain.Client == null) { return; }
distributor.item.CreateClientEvent(distributor, new EventData(this, EventType.NameChange));
};
nameBox.GetChild<GUIFrame>().GetChild<GUICustomComponent>().OnDrawToolTip = comp =>
{
if (Screen.Selected != GameMain.SubEditorScreen && !nameBox.Selected)
{
comp.ToolTip = null;
return;
}
LocalizedString localizedText = TextManager.Get(nameBox.Text);
comp.ToolTip = localizedText.IsNullOrEmpty()
? TextManager.GetWithVariable("StringPropertyCannotTranslate", "[tag]", nameBox.Text)
: TextManager.GetWithVariable("StringPropertyTranslate", "[translation]", localizedText);
};
GUITextBlock loadDisplay = GUI.CreateDigitalDisplay(new RectTransform(new Vector2(0.35f, 0.33f), groupContent.RectTransform, Anchor.TopRight) { AbsoluteOffset = (5, 0) },
out loadDisplayNameLabel, out GUITextBlock loadDisplayUnitLabel, TextManager.Get("PowerTransferLoadLabel"), tooltip: TextManager.Get("PowerTransferTipLoad"), leftLabelFont: GUIStyle.Font);
out GUITextBlock? _, out GUITextBlock loadDisplayUnitLabel, TextManager.Get("PowerTransferLoadLabel"), tooltip: TextManager.Get("PowerTransferTipLoad"), leftLabelFont: GUIStyle.Font);
loadDisplay.TextGetter = () => MathUtils.RoundToInt(Load).ToString();
float textAndPaddingWidth = loadDisplayNameLabel!.Font.MeasureString(loadDisplayNameLabel!.Text).X + loadDisplayNameLabel.Padding.X + loadDisplayNameLabel.Padding.Z;
float availableWidth = groupContent!.Rect.Width - loadDisplayNameLabel.Parent.Rect.Width + loadDisplayNameLabel.Rect.Width - textAndPaddingWidth;
nameGroup!.RectTransform.Resize(new Point((int)availableWidth, nameGroup.Rect.Height));
ratioSlider = new GUIScrollBar(new RectTransform(new Vector2(1f, 0.33f), groupContent.RectTransform, Anchor.Center), barSize: 0.15f, style: "DeviceSlider")
{
Step = SupplyRatioStep,
@@ -104,17 +78,16 @@ namespace Barotrauma.Items.Components
ratioSlider.Bar.RectTransform.MaxSize = new Point(ratioSlider.Bar.Rect.Height);
GUITextBlock ratioDisplay = GUI.CreateDigitalDisplay(new RectTransform(new Vector2(0.2f, 0.33f), groupContent.RectTransform, Anchor.BottomLeft),
out GUITextBlock? _, out GUITextBlock ratioDisplayUnitLabel,
out GUITextBlock? _, out GUITextBlock _,
rightLabelText: "%");
ratioDisplay.TextGetter = () => DisplayRatio.ToString();
GUITextBlock outputDisplay = GUI.CreateDigitalDisplay(new RectTransform(new Vector2(0.35f, 0.33f), groupContent.RectTransform, Anchor.BottomRight) { AbsoluteOffset = (5, 0) },
out GUITextBlock? outputDisplayNameLabel, out GUITextBlock outputDisplayUnitLabel,
out GUITextBlock? _, out GUITextBlock outputDisplayUnitLabel,
TextManager.Get("powerdistributor.supplylabel"), tooltip: TextManager.Get("PowerTransferTipPower"), leftLabelFont: GUIStyle.Font);
outputDisplay.TextGetter = () => distributor.IsShortCircuited(PowerOut) ? "err" : MathUtils.RoundToInt(distributor.CalculatePowerOut(this)).ToString();
powerUnitLabels.Add(loadDisplayUnitLabel);
powerUnitLabels.Add(ratioDisplayUnitLabel);
powerUnitLabels.Add(outputDisplayUnitLabel);
GUITextBlock.AutoScaleAndNormalize(powerUnitLabels);
@@ -138,14 +111,7 @@ namespace Barotrauma.Items.Components
IsVisible = PowerOut.Wires.Count >= 1;
frame!.Visible = IsVisible;
divider!.Visible = IsVisible && distributor.powerGroups.Last(group => group.frame!.Visible) != this;
if (distributor.prevLanguage != GameSettings.CurrentConfig.Language)
{
GUITextBlock.AutoScaleAndNormalize(powerUnitLabels);
float textAndPaddingWidth = loadDisplayNameLabel!.Font.MeasureString(loadDisplayNameLabel!.Text).X + loadDisplayNameLabel.Padding.X + loadDisplayNameLabel.Padding.Z;
float availableWidth = groupContent!.Rect.Width - loadDisplayNameLabel.Parent.Rect.Width + loadDisplayNameLabel.Rect.Width - textAndPaddingWidth;
nameGroup!.RectTransform.Resize(new Point((int)availableWidth, nameGroup.Rect.Height));
}
if (distributor.prevLanguage != GameSettings.CurrentConfig.Language) { GUITextBlock.AutoScaleAndNormalize(powerUnitLabels); }
}
}

View File

@@ -716,19 +716,13 @@ namespace Barotrauma.Items.Components
GetAvailablePower(out float batteryCharge, out float batteryCapacity);
List<Item> availableAmmo = [];
AddAmmoFromContainer(item.GetComponent<ItemContainer>());
List<Item> availableAmmo = new List<Item>();
foreach (MapEntity e in item.linkedTo)
{
if (e is not Item linkedItem) { continue; }
AddAmmoFromContainer(linkedItem.GetComponent<ItemContainer>());
}
void AddAmmoFromContainer(ItemContainer itemContainer)
{
if (itemContainer == null) { return; }
if (!(e is Item linkedItem)) { continue; }
var itemContainer = linkedItem.GetComponent<ItemContainer>();
if (itemContainer == null) { continue; }
availableAmmo.AddRange(itemContainer.Inventory.AllItems);
//add empty slots too
for (int i = 0; i < itemContainer.Inventory.Capacity - itemContainer.Inventory.AllItems.Count(); i++)
{
availableAmmo.Add(null);

View File

@@ -339,19 +339,6 @@ namespace Barotrauma
toolTip += $"‖color:{conditionColorStr}‖ ({(int)item.ConditionPercentage} %)‖color:end‖";
}
if (!description.IsNullOrEmpty()) { toolTip += '\n' + description; }
if (item.Prefab.UnlockedRecipeInToolTip.Length > 0 && GameMain.GameSession is { } GameSession)
{
if (item.Prefab.UnlockedRecipeInToolTip.All(id => GameSession.HasUnlockedRecipe(Character.Controlled, id)))
{
toolTip += $"\n‖color:{XMLExtensions.ToStringHex(GUIStyle.Green)}‖{TextManager.Get("unlockedrecipe.true")}‖color:end‖";
}
else
{
toolTip += $"\n‖color:{XMLExtensions.ToStringHex(GUIStyle.Yellow)}‖{TextManager.Get("unlockedrecipe.false")}‖color:end‖";
}
}
if (item.Prefab.ContentPackage != GameMain.VanillaContent && item.Prefab.ContentPackage != null)
{
colorStr = XMLExtensions.ToStringHex(Color.MediumPurple);
@@ -369,7 +356,19 @@ namespace Barotrauma
}
#if DEBUG
toolTip += $" ({item.Prefab.Identifier})";
#endif
#endif
if (!item.Prefab.UnlockedRecipeInToolTip.IsEmpty && GameMain.GameSession is { } GameSession)
{
if (GameSession.HasUnlockedRecipe(Character.Controlled, item.Prefab.UnlockedRecipeInToolTip))
{
toolTip += TextManager.Get("unlockedrecipe.true");
}
else
{
toolTip += $"\n‖color:{XMLExtensions.ToStringHex(GUIStyle.Yellow)}‖{TextManager.Get("unlockedrecipe.false")}‖color:end‖";
}
}
if (PlayerInput.KeyDown(InputType.ContextualCommand))
{
toolTip += $"\n‖color:gui.blue‖{TextManager.ParseInputTypes(TextManager.Get("itemmsgcontextualorders"))}‖color:end‖";
@@ -1301,7 +1300,8 @@ namespace Barotrauma
SubEditorScreen.StoreCommand(new InventoryPlaceCommand(DraggingItems.First().ParentInventory, new List<Item>(DraggingItems), true));
}
}
SoundPlayer.PlayUISound(GUISoundType.DropItem);
bool removed = false;
if (Screen.Selected is SubEditorScreen editor)
{

View File

@@ -75,11 +75,11 @@ internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenuBase
// ReSharper restore FieldCanBeMadeReadOnly.Local
private const string SettingsResetButtonText = $"{LuaCsSetup.PackageName}.SettingsMenu.ResetVisibleSettings";
private const string SettingsResetPromptTitle = $"{LuaCsSetup.PackageName}.SettingsMenu.ResetPrompt.Title";
private const string SettingsResetPromptContents = $"{LuaCsSetup.PackageName}.SettingsMenu.ResetPrompt.Message";
private const string SettingsResetPromptYesText = $"{LuaCsSetup.PackageName}.SettingsMenu.ResetPrompt.Yes";
private const string SettingsResetPromptNoText = $"{LuaCsSetup.PackageName}.SettingsMenu.ResetPrompt.No";
private const string SettingsResetButtonText = "LuaCsForBarotrauma.SettingsMenu.ResetVisibleSettings";
private const string SettingsResetPromptTitle = "LuaCsForBarotrauma.SettingsMenu.ResetPrompt.Title";
private const string SettingsResetPromptContents = "LuaCsForBarotrauma.SettingsMenu.ResetPrompt.Message";
private const string SettingsResetPromptYesText = "LuaCsForBarotrauma.SettingsMenu.ResetPrompt.Yes";
private const string SettingsResetPromptNoText = "LuaCsForBarotrauma.SettingsMenu.ResetPrompt.No";
private event Action OnApplyInstalledModsChanges;
@@ -100,7 +100,7 @@ internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenuBase
var menuTitleLayoutGroup = new GUILayoutGroup(
new RectTransform(new Vector2(1f, MenuTitleHeight), mainLayoutGroup.RectTransform, Anchor.TopLeft), true, Anchor.TopLeft);
GUIUtil.Label(menuTitleLayoutGroup,
GetLocalizedString($"{LuaCsSetup.PackageName}.SettingsMenu.ModGameplayButton", "Mod Gameplay Settings"),
GetLocalizedString("LuaCsForBarotrauma.SettingsMenu.ModGameplayButton", "Mod Gameplay Settings"),
GUIStyle.LargeFont, new Vector2(1f, 1f));
// page contents

View File

@@ -46,10 +46,10 @@ public class SettingsMenuSystem : ISettingsMenuSystem
var tabControlsIndex = (SettingsMenu.Tab)tabCount+1;
_gameplayContentFrame = CreateNewContentTab(tabGameplayIndex, __instance,
GUIStyle.ComponentStyles.ContainsKey("SettingsMenuTab.LuaCsSettings") ? "SettingsMenuTab.LuaCsSettings" : "SettingsMenuTab.Mods",
$"{LuaCsSetup.PackageName}.SettingsMenu.ModGameplayButton");
GUIStyle.ComponentStyles.ContainsKey("SettingsMenuTab.LuaCsSettings") ? "SettingsMenuTab.LuaCsSettings" : "SettingsMenuTab.Mods",
"LuaCsForBarotrauma.SettingsMenu.ModGameplayButton");
/*_controlsContentFrame = CreateNewContentTab(tabControlsIndex, __instance,
"SettingsMenuTab.Controls", $"{LuaCsSetup.PackageName}.SettingsMenu.ModControlsButton");
"SettingsMenuTab.Controls", "LuaCsForBarotrauma.SettingsMenu.ModControlsButton");
*/
_gameplayMenuInstance = new ModsGameplaySettingsMenu(_gameplayContentFrame, _packageManagementService, _configService, _loggerService, __instance);

View File

@@ -8,7 +8,7 @@ using System.Xml.Linq;
namespace Barotrauma
{
class BackgroundCreature : ISteerable, ILevelRenderableObject
class BackgroundCreature : ISteerable
{
const float MaxDepth = 10000.0f;
@@ -76,8 +76,6 @@ namespace Barotrauma
set;
}
public Vector3 Position => new Vector3(position.X, position.Y, Depth);
public BackgroundCreature(BackgroundCreaturePrefab prefab, Vector2 position)
{
this.Prefab = prefab;

View File

@@ -3,6 +3,7 @@ using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma
{
@@ -14,11 +15,52 @@ namespace Barotrauma
private float checkVisibleTimer;
private readonly List<BackgroundCreature> creatures = [];
private readonly List<BackgroundCreature> creatures = new List<BackgroundCreature>();
private readonly List<BackgroundCreature> visibleCreatures = [];
private readonly List<BackgroundCreature> visibleCreatures = new List<BackgroundCreature>();
public IEnumerable<BackgroundCreature> VisibleCreatures => visibleCreatures;
public BackgroundCreatureManager()
{
/*foreach(var file in files)
{
LoadConfig(file.Path);
}*/
}
/*public BackgroundCreatureManager(string path)
{
DebugConsole.AddWarning($"Couldn't find any BackgroundCreaturePrefabs files, falling back to {path}");
LoadConfig(ContentPath.FromRaw(null, path));
}
private void LoadConfig(ContentPath configPath)
{
try
{
XDocument doc = XMLExtensions.TryLoadXml(configPath);
if (doc == null) { return; }
var mainElement = doc.Root.FromPackage(configPath.ContentPackage);
if (mainElement.IsOverride())
{
mainElement = mainElement.FirstElement();
Prefabs.Clear();
DebugConsole.NewMessage($"Overriding all background creatures with '{configPath}'", Color.MediumPurple);
}
else if (Prefabs.Any())
{
DebugConsole.NewMessage($"Loading additional background creatures from file '{configPath}'");
}
foreach (var element in mainElement.Elements())
{
Prefabs.Add(new BackgroundCreaturePrefab(element));
};
}
catch (Exception e)
{
DebugConsole.ThrowError(String.Format("Failed to load BackgroundCreatures from {0}", configPath), e);
}
}*/
public void SpawnCreatures(Level level, int count, Vector2? position = null)
{
@@ -119,6 +161,14 @@ namespace Barotrauma
}
}
public void Draw(SpriteBatch spriteBatch, Camera cam)
{
foreach (BackgroundCreature creature in visibleCreatures)
{
creature.Draw(spriteBatch, cam);
}
}
public void DrawLights(SpriteBatch spriteBatch, Camera cam)
{
foreach (BackgroundCreature creature in visibleCreatures)

View File

@@ -140,7 +140,7 @@ namespace Barotrauma
public void DrawFront(SpriteBatch spriteBatch, Camera cam)
{
renderer?.DrawForeground(spriteBatch, cam, backgroundCreatureManager, LevelObjectManager);
renderer?.DrawForeground(spriteBatch, cam, LevelObjectManager);
}
public void ClientEventRead(IReadMessage msg, float sendingTime)
{

View File

@@ -12,7 +12,7 @@ using System.Xml.Linq;
namespace Barotrauma
{
partial class LevelObject : ILevelRenderableObject
partial class LevelObject
{
public float SwingTimer;
public float ScaleOscillateTimer;

View File

@@ -8,20 +8,13 @@ using System.Linq;
namespace Barotrauma
{
public interface ILevelRenderableObject
{
public Vector3 Position { get; }
}
partial class LevelObjectManager
{
// Pre-initialized to the max size, so that we don't have to resize the lists at runtime. TODO: Could the capacity (of some collections?) be lower?
private readonly List<ILevelRenderableObject> visibleObjectsBack = new List<ILevelRenderableObject>(MaxVisibleObjects);
private readonly List<ILevelRenderableObject> visibleObjectsMid = new List<ILevelRenderableObject>(MaxVisibleObjects);
private readonly List<ILevelRenderableObject> visibleObjectsFront = new List<ILevelRenderableObject>(MaxVisibleObjects);
private readonly HashSet<ILevelRenderableObject> allVisibleObjects = new HashSet<ILevelRenderableObject>(MaxVisibleObjects);
private readonly List<LevelObject> visibleObjectsBack = new List<LevelObject>(MaxVisibleObjects);
private readonly List<LevelObject> visibleObjectsMid = new List<LevelObject>(MaxVisibleObjects);
private readonly List<LevelObject> visibleObjectsFront = new List<LevelObject>(MaxVisibleObjects);
private readonly HashSet<LevelObject> allVisibleObjects = new HashSet<LevelObject>(MaxVisibleObjects);
private double NextRefreshTime;
@@ -42,44 +35,35 @@ namespace Barotrauma
partial void UpdateProjSpecific(float deltaTime, Camera cam)
{
foreach (ILevelRenderableObject obj in visibleObjectsBack)
foreach (LevelObject obj in visibleObjectsBack)
{
if (obj is LevelObject levelObj)
{
levelObj.Update(deltaTime, cam);
}
obj.Update(deltaTime, cam);
}
foreach (ILevelRenderableObject obj in visibleObjectsMid)
foreach (LevelObject obj in visibleObjectsMid)
{
if (obj is LevelObject levelObj)
{
levelObj.Update(deltaTime, cam);
}
obj.Update(deltaTime, cam);
}
foreach (ILevelRenderableObject obj in visibleObjectsFront)
foreach (LevelObject obj in visibleObjectsFront)
{
if (obj is LevelObject levelObj)
{
levelObj.Update(deltaTime, cam);
}
obj.Update(deltaTime, cam);
}
}
/// <summary>
/// Returns all visible objects, but not in order, because internally uses a HashSet.
/// </summary>
public IEnumerable<ILevelRenderableObject> GetAllVisibleObjects()
public IEnumerable<LevelObject> GetAllVisibleObjects()
{
allVisibleObjects.Clear();
foreach (ILevelRenderableObject obj in visibleObjectsBack)
foreach (LevelObject obj in visibleObjectsBack)
{
allVisibleObjects.Add(obj);
}
foreach (ILevelRenderableObject obj in visibleObjectsMid)
foreach (LevelObject obj in visibleObjectsMid)
{
allVisibleObjects.Add(obj);
}
foreach (ILevelRenderableObject obj in visibleObjectsFront)
foreach (LevelObject obj in visibleObjectsFront)
{
allVisibleObjects.Add(obj);
}
@@ -89,7 +73,7 @@ namespace Barotrauma
/// <summary>
/// Checks which level objects are in camera view and adds them to the visibleObjects lists
/// </summary>
private void RefreshVisibleObjects(Rectangle currentIndices, BackgroundCreatureManager backgroundCreatureManager, float zoom)
private void RefreshVisibleObjects(Rectangle currentIndices, float zoom)
{
visibleObjectsBack.Clear();
visibleObjectsMid.Clear();
@@ -168,27 +152,6 @@ namespace Barotrauma
}
}
foreach (var backgroundCreature in backgroundCreatureManager.VisibleCreatures)
{
int drawOrderIndex = 0;
for (int i = 0; i < visibleObjectsBack.Count; i++)
{
if (visibleObjectsBack[i].Position.Z > backgroundCreature.Position.Z)
{
break;
}
else
{
drawOrderIndex = i + 1;
if (drawOrderIndex >= MaxVisibleObjects) { break; }
}
}
if (drawOrderIndex >= 0 && drawOrderIndex < MaxVisibleObjects)
{
visibleObjectsBack.Insert(drawOrderIndex, backgroundCreature);
}
}
//object grid is sorted in an ascending order
//(so we prefer the objects in the foreground instead of ones in the background if some need to be culled)
//rendering needs to be done in a descending order though to get the background objects to be drawn first -> reverse the lists
@@ -202,28 +165,28 @@ namespace Barotrauma
/// <summary>
/// Draw the objects behind the level walls
/// </summary>
public void DrawObjectsBack(SpriteBatch spriteBatch, BackgroundCreatureManager backgroundCreatureManager, Camera cam)
public void DrawObjectsBack(SpriteBatch spriteBatch, Camera cam)
{
DrawObjects(spriteBatch, cam, backgroundCreatureManager, visibleObjectsBack);
DrawObjects(spriteBatch, cam, visibleObjectsBack);
}
/// <summary>
/// Draw the objects in front of the level walls, but behind characters
/// </summary>
public void DrawObjectsMid(SpriteBatch spriteBatch, BackgroundCreatureManager backgroundCreatureManager, Camera cam)
public void DrawObjectsMid(SpriteBatch spriteBatch, Camera cam)
{
DrawObjects(spriteBatch, cam, backgroundCreatureManager, visibleObjectsMid);
DrawObjects(spriteBatch, cam, visibleObjectsMid);
}
/// <summary>
/// Draw the objects in front of the level walls and characters
/// </summary>
public void DrawObjectsFront(SpriteBatch spriteBatch, BackgroundCreatureManager backgroundCreatureManager, Camera cam)
public void DrawObjectsFront(SpriteBatch spriteBatch, Camera cam)
{
DrawObjects(spriteBatch, cam, backgroundCreatureManager, visibleObjectsFront);
DrawObjects(spriteBatch, cam, visibleObjectsFront);
}
private void DrawObjects(SpriteBatch spriteBatch, Camera cam, BackgroundCreatureManager backgroundCreatureManager, List<ILevelRenderableObject> objectList)
private void DrawObjects(SpriteBatch spriteBatch, Camera cam, List<LevelObject> objectList)
{
Rectangle indices = Rectangle.Empty;
indices.X = (int)Math.Floor(cam.WorldView.X / (float)GridSize);
@@ -244,7 +207,7 @@ namespace Barotrauma
float z = 0.0f;
if (ForceRefreshVisibleObjects || (currentGridIndices != indices && Timing.TotalTime > NextRefreshTime))
{
RefreshVisibleObjects(indices, backgroundCreatureManager, cam.Zoom);
RefreshVisibleObjects(indices, cam.Zoom);
ForceRefreshVisibleObjects = false;
if (cam.Zoom < 0.1f)
{
@@ -253,93 +216,61 @@ namespace Barotrauma
}
}
bool prevObjectHasDeformableSprite = false;
foreach (ILevelRenderableObject obj2 in objectList)
foreach (LevelObject obj in objectList)
{
Vector2 camDiff = new Vector2(obj2.Position.X, obj2.Position.Y) - cam.WorldViewCenter;
Vector2 camDiff = new Vector2(obj.Position.X, obj.Position.Y) - cam.WorldViewCenter;
camDiff.Y = -camDiff.Y;
Sprite activeSprite = obj.Sprite;
activeSprite?.Draw(
spriteBatch,
new Vector2(obj.Position.X, -obj.Position.Y) - camDiff * obj.Position.Z * ParallaxStrength,
Color.Lerp(obj.Prefab.SpriteColor, obj.Prefab.SpriteColor.Multiply(Level.Loaded.BackgroundTextureColor), obj.Position.Z / obj.Prefab.FadeOutDepth),
activeSprite.Origin,
obj.CurrentRotation,
obj.CurrentScale,
SpriteEffects.None,
z);
bool hasDeformableSprite = false;
if (obj2 is LevelObject levelObject)
if (obj.ActivePrefab.DeformableSprite != null)
{
hasDeformableSprite = levelObject.ActivePrefab.DeformableSprite != null;
if (hasDeformableSprite != prevObjectHasDeformableSprite)
if (obj.CurrentSpriteDeformation != null)
{
spriteBatch.End();
spriteBatch.Begin(SpriteSortMode.Deferred,
BlendState.NonPremultiplied,
SamplerState.LinearWrap, DepthStencilState.DepthRead,
transformMatrix: cam.Transform);
obj.ActivePrefab.DeformableSprite.Deform(obj.CurrentSpriteDeformation);
}
Sprite activeSprite = levelObject.Sprite;
activeSprite?.Draw(
spriteBatch,
new Vector2(levelObject.Position.X, -levelObject.Position.Y) - camDiff * levelObject.Position.Z * ParallaxStrength,
Color.Lerp(levelObject.Prefab.SpriteColor, levelObject.Prefab.SpriteColor.Multiply(Level.Loaded.BackgroundTextureColor), levelObject.Position.Z / levelObject.Prefab.FadeOutDepth),
activeSprite.Origin,
levelObject.CurrentRotation,
levelObject.CurrentScale,
SpriteEffects.None,
z);
if (hasDeformableSprite)
else
{
if (levelObject.CurrentSpriteDeformation != null)
{
levelObject.ActivePrefab.DeformableSprite.Deform(levelObject.CurrentSpriteDeformation);
}
else
{
levelObject.ActivePrefab.DeformableSprite.Reset();
}
levelObject.ActivePrefab.DeformableSprite?.Draw(cam,
new Vector3(new Vector2(levelObject.Position.X, levelObject.Position.Y) - camDiff * levelObject.Position.Z * ParallaxStrength, z * 10.0f),
levelObject.ActivePrefab.DeformableSprite.Origin,
levelObject.CurrentRotation,
levelObject.CurrentScale,
Color.Lerp(levelObject.Prefab.SpriteColor, levelObject.Prefab.SpriteColor.Multiply(Level.Loaded.BackgroundTextureColor), levelObject.Position.Z / 5000.0f));
obj.ActivePrefab.DeformableSprite.Reset();
}
prevObjectHasDeformableSprite = hasDeformableSprite;
if (GameMain.DebugDraw)
{
GUI.DrawRectangle(spriteBatch, new Vector2(levelObject.Position.X, -levelObject.Position.Y), new Vector2(10.0f, 10.0f), GUIStyle.Red, true);
if (levelObject.Triggers == null) { continue; }
foreach (LevelTrigger trigger in levelObject.Triggers)
{
if (trigger.PhysicsBody == null) continue;
GUI.DrawLine(spriteBatch, new Vector2(levelObject.Position.X, -levelObject.Position.Y), new Vector2(trigger.WorldPosition.X, -trigger.WorldPosition.Y), Color.Cyan, 0, 3);
Vector2 flowForce = trigger.GetWaterFlowVelocity();
if (flowForce.LengthSquared() > 1)
{
flowForce.Y = -flowForce.Y;
GUI.DrawLine(spriteBatch, new Vector2(trigger.WorldPosition.X, -trigger.WorldPosition.Y), new Vector2(trigger.WorldPosition.X, -trigger.WorldPosition.Y) + flowForce * 10, GUIStyle.Orange, 0, 5);
}
trigger.PhysicsBody.UpdateDrawPosition();
trigger.PhysicsBody.DebugDraw(spriteBatch, trigger.IsTriggered ? Color.Cyan : Color.DarkCyan);
}
}
obj.ActivePrefab.DeformableSprite?.Draw(cam,
new Vector3(new Vector2(obj.Position.X, obj.Position.Y) - camDiff * obj.Position.Z * ParallaxStrength, z * 10.0f),
obj.ActivePrefab.DeformableSprite.Origin,
obj.CurrentRotation,
obj.CurrentScale,
Color.Lerp(obj.Prefab.SpriteColor, obj.Prefab.SpriteColor.Multiply(Level.Loaded.BackgroundTextureColor), obj.Position.Z / 5000.0f));
}
else if (obj2 is BackgroundCreature backgroundCreature && cam.Zoom > 0.05f)
if (GameMain.DebugDraw)
{
hasDeformableSprite = backgroundCreature.Prefab.DeformableSprite != null;
if (hasDeformableSprite != prevObjectHasDeformableSprite)
GUI.DrawRectangle(spriteBatch, new Vector2(obj.Position.X, -obj.Position.Y), new Vector2(10.0f, 10.0f), GUIStyle.Red, true);
if (obj.Triggers == null) { continue; }
foreach (LevelTrigger trigger in obj.Triggers)
{
spriteBatch.End();
spriteBatch.Begin(SpriteSortMode.Deferred,
BlendState.NonPremultiplied,
SamplerState.LinearWrap, DepthStencilState.DepthRead,
transformMatrix: cam.Transform);
if (trigger.PhysicsBody == null) continue;
GUI.DrawLine(spriteBatch, new Vector2(obj.Position.X, -obj.Position.Y), new Vector2(trigger.WorldPosition.X, -trigger.WorldPosition.Y), Color.Cyan, 0, 3);
Vector2 flowForce = trigger.GetWaterFlowVelocity();
if (flowForce.LengthSquared() > 1)
{
flowForce.Y = -flowForce.Y;
GUI.DrawLine(spriteBatch, new Vector2(trigger.WorldPosition.X, -trigger.WorldPosition.Y), new Vector2(trigger.WorldPosition.X, -trigger.WorldPosition.Y) + flowForce * 10, GUIStyle.Orange, 0, 5);
}
trigger.PhysicsBody.UpdateDrawPosition();
trigger.PhysicsBody.DebugDraw(spriteBatch, trigger.IsTriggered ? Color.Cyan : Color.DarkCyan);
}
backgroundCreature.Draw(spriteBatch, cam);
}
prevObjectHasDeformableSprite = hasDeformableSprite;
z += 0.0001f;
}

View File

@@ -214,9 +214,8 @@ namespace Barotrauma
//calculate the sum of the forces of nearby level triggers
//and use it to move the water texture and water distortion effect
Vector2 currentWaterParticleVel = level.GenerationParams.WaterParticleVelocity;
foreach (ILevelRenderableObject obj in level.LevelObjectManager.GetAllVisibleObjects())
foreach (LevelObject levelObject in level.LevelObjectManager.GetAllVisibleObjects())
{
if (obj is not LevelObject levelObject) { continue; }
if (levelObject.Triggers == null) { continue; }
//use the largest water flow velocity of all the triggers
Vector2 objectMaxFlow = Vector2.Zero;
@@ -275,7 +274,11 @@ namespace Barotrauma
SamplerState.LinearWrap, DepthStencilState.DepthRead, null, null,
cam.Transform);
backgroundSpriteManager?.DrawObjectsBack(spriteBatch, backgroundCreatureManager, cam);
backgroundSpriteManager?.DrawObjectsBack(spriteBatch, cam);
if (cam.Zoom > 0.05f)
{
backgroundCreatureManager?.Draw(spriteBatch, cam);
}
level.GenerationParams.DrawWaterParticles(spriteBatch, cam, waterParticleOffset);
@@ -289,18 +292,17 @@ namespace Barotrauma
BlendState.NonPremultiplied,
SamplerState.LinearClamp, DepthStencilState.DepthRead, null, null,
cam.Transform);
backgroundSpriteManager?.DrawObjectsMid(spriteBatch, backgroundCreatureManager, cam);
backgroundSpriteManager?.DrawObjectsMid(spriteBatch, cam);
spriteBatch.End();
}
public void DrawForeground(SpriteBatch spriteBatch, Camera cam,
BackgroundCreatureManager backgroundCreatureManager, LevelObjectManager backgroundSpriteManager = null)
public void DrawForeground(SpriteBatch spriteBatch, Camera cam, LevelObjectManager backgroundSpriteManager = null)
{
spriteBatch.Begin(SpriteSortMode.Deferred,
BlendState.NonPremultiplied,
SamplerState.LinearClamp, DepthStencilState.DepthRead, null, null,
cam.Transform);
backgroundSpriteManager?.DrawObjectsFront(spriteBatch, backgroundCreatureManager, cam);
backgroundSpriteManager?.DrawObjectsFront(spriteBatch, cam);
spriteBatch.End();
}

View File

@@ -2547,7 +2547,7 @@ namespace Barotrauma.Networking
segmentTable.StartNewSegment(ClientNetSegment.SyncIds);
//outmsg.Write(GameMain.NetLobbyScreen.LastUpdateID);
outmsg.WriteUInt16(ChatMessage.LastID);
outmsg.WriteUInt16(SpoofEntityManagerReceivedId ? (ushort)1 : EntityEventManager.LastReceivedID);
outmsg.WriteUInt16(EntityEventManager.LastReceivedID);
outmsg.WriteUInt16(LastClientListUpdateID);
if (!(GameMain.GameSession?.GameMode is MultiPlayerCampaign campaign) || campaign.LastSaveID == 0)
@@ -3370,12 +3370,6 @@ namespace Barotrauma.Networking
{
get { return votingInterface; }
}
/// <summary>
/// Forces the client to report the last received event ID as always being 1, making the server believe the client is always behind.
/// </summary>
public bool SpoofEntityManagerReceivedId { get; set; }
private VotingInterface votingInterface;
public bool TypingChatMessage(GUITextBox textBox, string text)

View File

@@ -89,7 +89,6 @@ namespace Barotrauma.Networking
{
byte queueId = msg.ReadByte();
float distanceFactor = msg.ReadRangedSingle(0.0f, 1.0f, 8);
bool isRadio = msg.ReadBoolean();
VoipQueue queue = queues.Find(q => q.QueueID == queueId);
if (queue == null)
@@ -118,21 +117,19 @@ namespace Barotrauma.Networking
float rangeMultiplier = spectating ? 2.0f : 1.0f;
WifiComponent senderRadio = null;
var messageType = isRadio ? ChatMessageType.Radio : ChatMessageType.Default;
var messageType =
!client.VoipQueue.ForceLocal &&
ChatMessage.CanUseRadio(client.Character, out senderRadio) &&
(spectating || (ChatMessage.CanUseRadio(Character.Controlled, out var recipientRadio) && senderRadio.CanReceive(recipientRadio)))
? ChatMessageType.Radio : ChatMessageType.Default;
client.Character.ShowTextlessSpeechBubble(1.25f, ChatMessage.MessageColor[(int)messageType]);
client.VoipSound.UseRadioFilter = messageType == ChatMessageType.Radio && !GameSettings.CurrentConfig.Audio.DisableVoiceChatFilters;
client.RadioNoise = 0.0f;
if (messageType == ChatMessageType.Radio)
{
//If the client cannot establish a radio, use a headsets default range as a fallback to calculate the radio noise.
//This cannot happen in an un-modded setting as CanUseRadio is part of the server side check for isRadio to be true.
ChatMessage.CanUseRadio(client.Character, out senderRadio);
float senderRadioRange = (senderRadio == null) ? 35000.0f : senderRadio.Range;
client.VoipSound.UsingRadio = true;
client.VoipSound.SetRange(senderRadioRange * RangeNear * speechImpedimentMultiplier * rangeMultiplier, senderRadioRange * speechImpedimentMultiplier * rangeMultiplier);
client.VoipSound.SetRange(senderRadio.Range * RangeNear * speechImpedimentMultiplier * rangeMultiplier, senderRadio.Range * speechImpedimentMultiplier * rangeMultiplier);
if (distanceFactor > RangeNear && !spectating)
{
//noise starts increasing exponentially after 40% range

View File

@@ -533,18 +533,6 @@ namespace Barotrauma
return true;
}
};
new GUITickBox(new RectTransform(new Point(300, 30), Frame.RectTransform, Anchor.TopRight) { AbsoluteOffset = new Point(40, 280) },
"Lenient server startup timeouts")
{
Selected = NetConfig.UseLenientHandshake,
ToolTip = "Start with more lenient Lidgren handshake timeouts. The server is more likely to start even when running multiple instances on the same machine under heavy load.",
OnSelected = (tickBox) =>
{
NetConfig.UseLenientHandshake = tickBox.Selected;
return true;
}
};
#endif
var minButtonSize = new Point(120, 20);
@@ -1138,11 +1126,6 @@ namespace Barotrauma
}
#endif
if (NetConfig.UseLenientHandshake)
{
arguments.Add("-lenienthandshake");
}
var processInfo = new ProcessStartInfo
{
FileName = fileName,

View File

@@ -1,22 +1,19 @@
using Barotrauma.Extensions;
using Barotrauma.IO;
using Barotrauma.Items.Components;
using Barotrauma.Sounds;
using Barotrauma.Steam;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Steamworks;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Globalization;
using System.IO.Compression;
using System.Linq;
using System.Threading;
using System.Xml;
using System.Xml.Linq;
using Barotrauma.LuaCs.Events;
using Barotrauma.Sounds;
namespace Barotrauma
{
@@ -2047,8 +2044,6 @@ namespace Barotrauma
private bool SaveSubToFile(string name, ContentPackage packageToSaveTo)
{
bool remoteStorageWasEnabled = Submarine.MainSub.Info.SaveToRemoteStorage;
Type subFileType = DetermineSubFileType(MainSub?.Info.Type ?? SubmarineType.Player);
static string getExistingFilePath(ContentPackage package, string fileName)
@@ -2277,16 +2272,6 @@ namespace Barotrauma
linkedSubBox.AddItem(sub.Name, sub);
}
subNameLabel.Text = ToolBox.LimitString(MainSub.Info.Name, subNameLabel.Font, subNameLabel.Rect.Width);
if (remoteStorageWasEnabled)
{
Submarine.MainSub.Info.SaveToRemoteStorage = true;
RemoteStorageHelper.TryWrite(
localPath: MainSub.Info.FilePath,
saveAs: MainSub.Info.FilePath.CleanUpPathCrossPlatform(correctFilenameCase: false),
allowOverwrite: true);
}
}
}
}
@@ -3238,42 +3223,6 @@ namespace Barotrauma
previewImageButtonHolder.RectTransform.MinSize = new Point(0, previewImageButtonHolder.RectTransform.Children.Max(c => c.MinSize.Y));
if (SteamManager.IsInitialized)
{
GUILayoutGroup remoteStorageArea = new(
new RectTransform(new Vector2(1f, 0.05f), rightColumn.RectTransform,
minSize: new Point(0, minHeight)),
isHorizontal: true,
childAnchor: Anchor.CenterLeft)
{
Stretch = true,
AbsoluteSpacing = 5
};
new GUITextBlock(new RectTransform(Vector2.One, remoteStorageArea.RectTransform),
TextManager.Get("RemoteStorageToggle.Title"), textAlignment: Alignment.CenterLeft, wrap: true);
new GUITickBox(new RectTransform(Vector2.One, remoteStorageArea.RectTransform), label: "")
{
OnAddedToGUIUpdateList = component =>
{
// Values may change outside of game.
component.Enabled = SteamRemoteStorage.IsCloudEnabledForAccount;
component.ToolTip = !SteamRemoteStorage.IsCloudEnabledForAccount ? TextManager.Get("RemoteStorageToggle.Disabled") : "";
((GUITickBox)component).SetSelected(SteamRemoteStorage.IsCloudEnabled && MainSub.Info.SaveToRemoteStorage, callOnSelected: false);
},
OnSelected = tickBox =>
{
if (tickBox.Selected && !SteamRemoteStorage.IsCloudEnabledForApp)
{
RemoteStorageHelper.AskToEnable(onAccepted: () => MainSub.Info.SaveToRemoteStorage = true);
return false;
}
return MainSub.Info.SaveToRemoteStorage = tickBox.Selected;
}
};
}
var contentPackageTabber = new GUILayoutGroup(new RectTransform((1.0f, 0.075f), rightColumn.RectTransform), isHorizontal: true);
GUIButton createTabberBtn(string labelTag)
@@ -3797,7 +3746,7 @@ namespace Barotrauma
}
var package = GetLocalPackageThatOwnsSub(subInfo);
if (package != null || subInfo.IsFromRemoteStorage)
if (package != null)
{
deleteBtn.Enabled = true;
}
@@ -3822,29 +3771,13 @@ namespace Barotrauma
searchBox.OnDeselected += (sender, userdata) => { searchTitle.Visible = sender.Text.IsNullOrEmpty(); };
searchBox.OnTextChanged += (textBox, text) => { FilterSubs(subList, text); return true; };
List<SubmarineInfo> allSubs = [.. SubmarineInfo.SavedSubmarines];
foreach (SteamRemoteStorage.RemoteFile remoteFile in SteamRemoteStorage.Files.Where(file => file.Filename.EndsWith(".sub")))
{
if (!remoteFile.TryRead(out byte[] bytes)) { continue; }
using System.IO.MemoryStream stream = new(bytes);
using GZipStream zipStream = new(stream, CompressionMode.Decompress);
if (XMLExtensions.TryLoadXml(zipStream) is not XDocument doc)
{
DebugConsole.ThrowError($"{RemoteStorageHelper.DebugPrefix} Failed to load submarine \"{remoteFile.Filename}\" from remote storage: file is not a valid XML document.");
continue;
}
SubmarineInfo subInfo = new(remoteFile.Filename, element: doc.Root, tryLoad: false) { IsFromRemoteStorage = true };
allSubs.Add(subInfo);
}
IOrderedEnumerable<SubmarineInfo> sortedSubs = allSubs
.OrderBy(kvp => kvp.Type)
.ThenBy(kvp => kvp.Name)
.ThenBy(kvp => kvp.IsFromRemoteStorage);
var sortedSubs = GetLoadableSubs()
.OrderBy(s => s.Type)
.ThenBy(s => s.Name)
.ToList();
SubmarineInfo prevSub = null;
foreach (SubmarineInfo sub in sortedSubs)
{
if (prevSub == null || prevSub.Type != sub.Type)
@@ -3862,44 +3795,35 @@ namespace Barotrauma
prevSub = sub;
}
string displayPath = sub.FilePath;
if (sub.IsFromRemoteStorage)
string pathWithoutUserName = Path.GetFullPath(sub.FilePath);
string saveFolder = Path.GetFullPath(SaveUtil.DefaultSaveFolder);
if (pathWithoutUserName.StartsWith(saveFolder))
{
displayPath += $" {TextManager.Get("RemoteStorage")}";
pathWithoutUserName = "..." + pathWithoutUserName[saveFolder.Length..];
}
else
{
string saveFolder = Path.GetFullPath(SaveUtil.DefaultSaveFolder);
string fullPath = Path.GetFullPath(displayPath);
if (fullPath.StartsWith(saveFolder))
{
displayPath = $"...{fullPath[saveFolder.Length..]}";
}
pathWithoutUserName = sub.FilePath;
}
LocalizedString limitedName = ToolBox.LimitString(sub.Name, GUIStyle.Font, subList.Rect.Width - 80);
GUITextBlock textBlock = new(new RectTransform(Vector2.UnitX, subList.Content.RectTransform) { MinSize = new Point(0, 30) }, limitedName)
GUITextBlock textBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), subList.Content.RectTransform) { MinSize = new Point(0, 30) },
ToolBox.LimitString(sub.Name, GUIStyle.Font, subList.Rect.Width - 80))
{
UserData = sub,
ToolTip = displayPath
ToolTip = pathWithoutUserName
};
if (sub.IsFromRemoteStorage)
{
// remote storage
textBlock.OverrideTextColor(RemoteStorageHelper.SteamColor);
}
else if (ContentPackageManager.VanillaCorePackage == null || ContentPackageManager.VanillaCorePackage.Files.None(f => f.Path == sub.FilePath))
if (!(ContentPackageManager.VanillaCorePackage?.Files.Any(f => f.Path == sub.FilePath) ?? false))
{
if (GetLocalPackageThatOwnsSub(sub) == null &&
ContentPackageManager.AllPackages.FirstOrDefault(p => p.Files.Any(f => f.Path == sub.FilePath)) is ContentPackage subPackage)
{
// workshop mod
//workshop mod
textBlock.OverrideTextColor(Color.MediumPurple);
}
else
{
// local mod
//local mod
textBlock.OverrideTextColor(GUIStyle.TextColorBright);
}
}
@@ -4094,9 +4018,10 @@ namespace Barotrauma
return false;
}
if (subList.SelectedComponent?.UserData is not SubmarineInfo selectedSubInfo) { return false; }
if (!(subList.SelectedComponent?.UserData is SubmarineInfo selectedSubInfo)) { return false; }
if (!selectedSubInfo.IsFromRemoteStorage && GetLocalPackageThatOwnsSub(selectedSubInfo) is null)
var ownerPackage = GetLocalPackageThatOwnsSub(selectedSubInfo);
if (ownerPackage is null)
{
if (IsVanillaSub(selectedSubInfo))
{
@@ -4258,23 +4183,21 @@ namespace Barotrauma
{
if (sub == null) { return; }
// If the sub is included in a content package that only defines that one sub,
// check that it's a local content package and only allow deletion if it is.
// (deleting from the Submarines folder is also currently allowed, but this is temporary)
ContentPackage subPackage = GetLocalPackageThatOwnsSub(sub);
if (!ContentPackageManager.LocalPackages.Regular.Contains(subPackage)) { subPackage = null; }
if (!sub.IsFromRemoteStorage && subPackage == null) { return; }
GUIMessageBox msgBox = new(TextManager.Get("DeleteDialogLabel"), TextManager.GetWithVariable("DeleteDialogQuestion", "[file]", sub.Name), [TextManager.Get("Yes"), TextManager.Get("Cancel")]);
//If the sub is included in a content package that only defines that one sub,
//check that it's a local content package and only allow deletion if it is.
//(deleting from the Submarines folder is also currently allowed, but this is temporary)
var subPackage = GetLocalPackageThatOwnsSub(sub);
if (!ContentPackageManager.LocalPackages.Regular.Contains(subPackage)) { return; }
var msgBox = new GUIMessageBox(
TextManager.Get("DeleteDialogLabel"),
TextManager.GetWithVariable("DeleteDialogQuestion", "[file]", sub.Name),
new LocalizedString[] { TextManager.Get("Yes"), TextManager.Get("Cancel") });
msgBox.Buttons[0].OnClicked += (btn, userData) =>
{
if (sub.IsFromRemoteStorage)
try
{
RemoteStorageHelper.TryDelete(sub.FilePath);
}
else if (subPackage != null)
{
try
if (subPackage != null)
{
File.Delete(sub.FilePath, catchUnauthorizedAccessExceptions: false);
ModProject modProject = new ModProject(subPackage);
@@ -4285,17 +4208,17 @@ namespace Barotrauma
{
MainSub.Info.FilePath = null;
}
}
catch (Exception e)
{
DebugConsole.ThrowErrorLocalized(TextManager.GetWithVariable("DeleteFileError", "[file]", sub.FilePath), e);
}
}
sub.Dispose();
CreateLoadScreen();
}
sub.Dispose();
CreateLoadScreen();
return msgBox.Close(btn, userData);
catch (Exception e)
{
DebugConsole.ThrowErrorLocalized(TextManager.GetWithVariable("DeleteFileError", "[file]", sub.FilePath), e);
}
return true;
};
msgBox.Buttons[0].OnClicked += msgBox.Close;
msgBox.Buttons[1].OnClicked += msgBox.Close;
}

View File

@@ -445,7 +445,7 @@ namespace Barotrauma
{
DebugConsole.NewMessage("Missing Localization for property: " + propertyTag);
MissingLocalizations.Add($"sp.{propertyTag}.name|{displayName}");
MissingLocalizations.Add($"sp.{propertyTag}.description|{property.GetAttribute<Serialize>()?.Description}");
MissingLocalizations.Add($"sp.{propertyTag}.description|{property.GetAttribute<Serialize>().Description}");
}
}
#endif
@@ -467,7 +467,7 @@ namespace Barotrauma
}
if (toolTip.IsNullOrEmpty())
{
toolTip = property.GetAttribute<Serialize>()?.Description;
toolTip = property.GetAttribute<Serialize>().Description;
}
GUIComponent propertyField = null;

View File

@@ -1,34 +0,0 @@
#nullable enable
using Steamworks;
using System;
namespace Barotrauma.Steam;
internal static partial class RemoteStorageHelper
{
/// <summary>
/// Asks the user if they wish to enable remote storage. Accepting enables it automatically.
/// </summary>
/// <param name="onAccepted">Invoked when the user accepts enabling remote storage.</param>
/// <param name="onRejected">Invoked when the user rejects enabling remote storage.</param>
/// <remarks>Closes automatically if remote storage was enabled outside of the game, or if remote storage can not be enabled.</remarks>
public static void AskToEnable(Action? onAccepted = null, Action? onRejected = null)
{
GUIMessageBox confirmBox = new GUIMessageBox(
TextManager.Get("RemoteStorageEnablePopup.Header"),
TextManager.Get("RemoteStorageEnablePopup.Text"),
[TextManager.Get("Yes"), TextManager.Get("No")],
autoCloseCondition: () => !SteamRemoteStorage.IsCloudEnabledForAccount || SteamRemoteStorage.IsCloudEnabledForApp);
confirmBox.Buttons[0].OnClicked += (btn, data) =>
{
SteamRemoteStorage.IsCloudEnabledForApp = true;
onAccepted?.Invoke();
return confirmBox.Close(btn, data);
};
confirmBox.Buttons[1].OnClicked += (btn, data) =>
{
onRejected?.Invoke();
return confirmBox.Close(btn, data);
};
}
}

View File

@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product>
<Version>1.13.3.1</Version>
<Version>1.12.7.0</Version>
<Copyright>Copyright © FakeFish 2018-2024</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName>

View File

@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product>
<Version>1.13.3.1</Version>
<Version>1.12.7.0</Version>
<Copyright>Copyright © FakeFish 2018-2024</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName>

View File

@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product>
<Version>1.13.3.1</Version>
<Version>1.12.7.0</Version>
<Copyright>Copyright © FakeFish 2018-2024</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName>

View File

@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product>
<Version>1.13.3.1</Version>
<Version>1.12.7.0</Version>
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName>

View File

@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product>
<Version>1.13.3.1</Version>
<Version>1.12.7.0</Version>
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName>

View File

@@ -1799,35 +1799,22 @@ namespace Barotrauma
(Client client, Vector2 cursorWorldPos, string[] args) =>
{
if (Submarine.MainSub == null || Level.Loaded == null) { return; }
Submarine submarineToTeleport = Submarine.MainSub;
if (args.Length > 1)
{
foreach (Submarine sub in Submarine.Loaded.Where(s => s.PhysicsBody.BodyType == FarseerPhysics.BodyType.Dynamic))
{
if ((sub.Info.Name + "_" + sub.TeamID) == args[1])
{
submarineToTeleport = sub;
break;
}
}
}
if (args.Length == 0 || args[0].Equals("cursor", StringComparison.OrdinalIgnoreCase))
{
submarineToTeleport.SetPosition(cursorWorldPos);
Submarine.MainSub.SetPosition(cursorWorldPos);
}
else if (args[0].Equals("start", StringComparison.OrdinalIgnoreCase))
{
submarineToTeleport.SetPosition(Level.Loaded.StartPosition - Vector2.UnitY * submarineToTeleport.Borders.Height);
Submarine.MainSub.SetPosition(Level.Loaded.StartPosition - Vector2.UnitY * Submarine.MainSub.Borders.Height);
}
else if (args[0].Equals("end", StringComparison.OrdinalIgnoreCase))
{
submarineToTeleport.SetPosition(Level.Loaded.EndPosition - Vector2.UnitY * submarineToTeleport.Borders.Height);
Submarine.MainSub.SetPosition(Level.Loaded.EndPosition - Vector2.UnitY * Submarine.MainSub.Borders.Height);
}
else if (args[0].Equals("endoutpost", StringComparison.OrdinalIgnoreCase))
{
submarineToTeleport.SetPosition(Level.Loaded.EndExitPosition - Vector2.UnitY * submarineToTeleport.Borders.Height);
var submarineDockingPort = DockingPort.List.FirstOrDefault(d => d.Item.Submarine == submarineToTeleport);
Submarine.MainSub.SetPosition(Level.Loaded.EndExitPosition - Vector2.UnitY * Submarine.MainSub.Borders.Height);
var submarineDockingPort = DockingPort.List.FirstOrDefault(d => d.Item.Submarine == Submarine.MainSub);
if (Level.Loaded?.EndOutpost == null)
{
NewMessage("Can't teleport the sub to the end outpost (no outpost at the end of the level).", Color.Red);

View File

@@ -1179,7 +1179,6 @@ namespace Barotrauma
NetWalletTransfer transfer = INetSerializableStruct.Read<NetWalletTransfer>(msg);
if (GameMain.Server is null) { return; }
if (transfer.Amount <= 0) { return; }
if (transfer.Sender.TryUnwrap(out var id))
{

View File

@@ -193,6 +193,13 @@ namespace Barotrauma
(item.PreviousParentInventory == null ||
!sender.Character.CanAccessInventory(item.PreviousParentInventory));
// Prevent modified clients from being able to steal items from characters by item swapping with an existing item
// due to drag and drop being enabled
if (!sender.Character.CanAccessInventory(this, CharacterInventory.AccessLevel.AllowBotsAndPets) && GetItemAt(slotIndex) != null)
{
itemAccessDenied = true;
}
//more restricted "adding" of handcuffs: we can't allow putting handcuffs on a player just because dragging and dropping is allowed
if (item.HasTag(Tags.HandLockerItem) && !itemAccessDenied)
{
@@ -212,7 +219,7 @@ namespace Barotrauma
continue;
}
}
TryPutItem(item, slotIndex, allowSwapping: false, allowCombine: false, user: sender.Character, createNetworkEvent: false);
TryPutItem(item, slotIndex, true, true, sender.Character, false);
for (int j = 0; j < capacity; j++)
{
if (slots[j].Contains(item) && !receivedItemIdsFromClient[j].Contains(item.ID))

View File

@@ -8,17 +8,6 @@ namespace Barotrauma.Networking
{
partial class ChatMessage
{
private static string SanitizeText(Client client, string text)
{
if (!client.HasPermission(ClientPermissions.SpamImmunity))
{
// Prevent clients without spam immunity from being able to use RichString features
text = text.Replace('‖', ' ');
}
return text;
}
public static void ServerRead(IReadMessage msg, Client c)
{
c.KickAFKTimer = 0.0f;
@@ -80,7 +69,8 @@ namespace Barotrauma.Networking
txt = msg.ReadString() ?? "";
}
txt = SanitizeText(c, txt);
// Sanitize incoming text message from client so they can't use RichString features
txt = txt.Replace('‖', ' ');
if (!NetIdUtils.IdMoreRecent(ID, c.LastSentChatMsgID)) { return; }

View File

@@ -904,10 +904,7 @@ namespace Barotrauma.Networking
string subHash = inc.ReadString();
CampaignSettings settings = INetSerializableStruct.Read<CampaignSettings>(inc);
var matchingSub =
ServerSettings.AllowSubVoting ?
Voting.HighestVoted<SubmarineInfo>(VoteType.Sub, connectedClients) :
SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Name == subName && s.MD5Hash.StringRepresentation == subHash);
var matchingSub = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Name == subName && s.MD5Hash.StringRepresentation == subHash);
if (GameStarted)
{
@@ -1136,6 +1133,9 @@ namespace Barotrauma.Networking
Log(ClientLogName(c) + " has reported an error: " + errorStr, ServerLog.MessageType.Error);
GameAnalyticsManager.AddErrorEventOnce("GameServer.HandleClientError:" + errorStrNoName, GameAnalyticsManager.ErrorSeverity.Error, errorStr);
Log(
$"Entity event state at client error: pending={EntityEventManager.PendingCreateEventCount}, queued={EntityEventManager.EventCount}, unique={EntityEventManager.UniqueEventCount}, buffered={EntityEventManager.BufferedEventCount}, lastCreated={EntityEventManager.LastCreatedEventID}",
ServerLog.MessageType.Error);
try
{

View File

@@ -5,12 +5,8 @@ using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using static Barotrauma.EosInterface.Ownership;
// DO NOT TOUCH ANYTHING HERE
// OR EVERYTHING WILL FAIL
namespace Barotrauma.Networking
{
class ServerEntityEvent : NetEntityEvent
@@ -66,14 +62,42 @@ namespace Barotrauma.Networking
public List<ServerEntityEvent> Events
{
get { return events; }
get
{
FlushPendingCreates();
return events;
}
}
public List<ServerEntityEvent> UniqueEvents
{
get { return uniqueEvents; }
get
{
FlushPendingCreates();
return uniqueEvents;
}
}
public int PendingCreateEventCount => pendingCreateQueue.Count;
public int EventCount
{
get
{
FlushPendingCreates();
return events.Count;
}
}
public int UniqueEventCount
{
get
{
FlushPendingCreates();
return uniqueEvents.Count;
}
}
public int BufferedEventCount => bufferedEvents.Count;
public UInt16 LastCreatedEventID => ID;
private class BufferedEvent
{
public readonly Client Sender;
@@ -124,11 +148,6 @@ namespace Barotrauma.Networking
private readonly ConcurrentQueue<PendingCreateEvent> pendingCreateQueue;
private readonly Task createEventTask;
private readonly CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
private readonly SemaphoreSlim eventSignal = new SemaphoreSlim(0);
public ServerEntityEventManager(GameServer server)
{
events = new List<ServerEntityEvent>();
@@ -138,33 +157,24 @@ namespace Barotrauma.Networking
pendingCreateQueue = new ConcurrentQueue<PendingCreateEvent>();
lastWarningTime = -10.0;
SEM = this;
createEventTask = Task.Run(() => CreateEventProcessorLoop(cancellationTokenSource.Token));
}
private async Task CreateEventProcessorLoop(CancellationToken token)
public void FlushPendingCreates()
{
while (!token.IsCancellationRequested)
if (GameMain.MainThread != null && Thread.CurrentThread != GameMain.MainThread)
{
try
{
await eventSignal.WaitAsync(100, token);
ProcessPendingCreateEvents();
}
catch (OperationCanceledException)
{
break;
}
throw new InvalidOperationException($"{nameof(ServerEntityEventManager)} pending events must be flushed on the main thread.");
}
ProcessPendingCreateEvents();
}
private void ProcessPendingCreateEvents()
{
// Dequeue and process all pending events currently in the queue.
// Use a lock to synchronize modifications to shared lists / ID.
// CreateEntityEvent can be called from parallel update code. The queue keeps
// that enqueue path safe, while this method is only called from the main tick
// before reading or writing entity-event state.
while (pendingCreateQueue.TryDequeue(out PendingCreateEvent pending))
{
// The original CreateEvent logic (mostly unchanged) but executed under a lock
if (pending == null || pending.Entity == null) { continue; }
var entity = pending.Entity;
@@ -216,34 +226,18 @@ namespace Barotrauma.Networking
{
if (!ValidateEntity(entity)) { return; }
// enqueue and let background task handle the rest
pendingCreateQueue.Enqueue(new PendingCreateEvent(entity, extraData));
if (eventSignal.CurrentCount == 0)
{
eventSignal.Release();
}
}
public void Dispose()
{
cancellationTokenSource.Cancel();
eventSignal.Release();
try
{
createEventTask?.Wait(2000);
}
catch (AggregateException) { }
finally
{
cancellationTokenSource.Dispose();
eventSignal.Dispose();
}
ClearPendingCreates();
}
// Due to intensive access demend and time it takes to refactor, we use try-catch when facing thread-safety issue to skip to next update :(
public void Update(List<Client> clients)
{
FlushPendingCreates();
foreach (BufferedEvent bufferedEvent in bufferedEvents)
{
if (bufferedEvent.Character == null || bufferedEvent.Character.IsDead)
@@ -329,14 +323,7 @@ namespace Barotrauma.Networking
}
}
try
{
lastSentToAnyoneTime = events.ToList().Find(e => e.ID == lastSentToAnyone)?.CreateTime ?? Timing.TotalTime;
}
catch
{
lastSentToAnyoneTime = Timing.TotalTime;
}
lastSentToAnyoneTime = events.Find(e => e.ID == lastSentToAnyone)?.CreateTime ?? Timing.TotalTime;
if (Timing.TotalTime - lastWarningTime > 5.0 &&
@@ -353,15 +340,7 @@ namespace Barotrauma.Networking
clients.Where(c => c.NeedsMidRoundSync).ForEach(c => { if (NetIdUtils.IdMoreRecent(lastSentToAll, c.FirstNewEventID)) lastSentToAll = (ushort)(c.FirstNewEventID - 1); });
ServerEntityEvent firstEventToResend;
try
{
firstEventToResend = events.Find(e => e.ID == (ushort)(lastSentToAll + 1));
}
catch
{
firstEventToResend = null;
}
ServerEntityEvent firstEventToResend = events.Find(e => e.ID == (ushort)(lastSentToAll + 1));
if (firstEventToResend != null &&
GameMain.GameSession.RoundDuration > server.ServerSettings.RoundStartSyncDuration &&
@@ -373,7 +352,7 @@ namespace Barotrauma.Networking
// in which case we'll wait until the timeout runs out before kicking the client
List<Client> toKick = inGameClients.FindAll(c =>
NetIdUtils.IdMoreRecent((UInt16)(lastSentToAll + 1), c.LastRecvEntityEventID) &&
(!c.NeedsMidRoundSync || firstEventToResend.CreateTime > c.MidRoundSyncTimeOut || lastSentToAnyoneTime > c.MidRoundSyncTimeOut || Timing.TotalTime > c.MidRoundSyncTimeOut + 10.0));
(firstEventToResend.CreateTime > c.MidRoundSyncTimeOut || lastSentToAnyoneTime > c.MidRoundSyncTimeOut || Timing.TotalTime > c.MidRoundSyncTimeOut + 10.0));
toKick.ForEach(c =>
{
DebugConsole.NewMessage(c.Name + " was kicked because they were expecting a very old network event (" + (c.LastRecvEntityEventID + 1).ToString() + ")", Color.Red);
@@ -450,6 +429,8 @@ namespace Barotrauma.Networking
/// </summary>
public void Write(in SegmentTableWriter<ServerNetSegment> segmentTable, Client client, IWriteMessage msg, out List<NetEntityEvent> sentEvents)
{
FlushPendingCreates();
List<NetEntityEvent> eventsToSync = GetEventsToSync(client);
if (eventsToSync.Count == 0)
@@ -576,6 +557,8 @@ namespace Barotrauma.Networking
public void InitClientMidRoundSync(Client client)
{
FlushPendingCreates();
//no need for midround syncing if no events have been created,
//or if the first created unique event is still in the event list
if (uniqueEvents.Count == 0 || (events.Count > 0 && events[0].ID == uniqueEvents[0].ID))
@@ -693,6 +676,8 @@ namespace Barotrauma.Networking
public void Clear()
{
ClearPendingCreates();
ID = 0;
events.Clear();
@@ -709,5 +694,10 @@ namespace Barotrauma.Networking
c.LastSentEntityEventID = 0;
}
}
private void ClearPendingCreates()
{
while (pendingCreateQueue.TryDequeue(out _)) { }
}
}
}

View File

@@ -54,14 +54,13 @@ namespace Barotrauma.Networking
{
if (recipient == sender) { continue; }
if (!CanReceive(sender, recipient, out float distanceFactor, out bool isRadio)) { continue; }
if (!CanReceive(sender, recipient, out float distanceFactor)) { continue; }
IWriteMessage msg = new WriteOnlyMessage();
msg.WriteByte((byte)ServerPacketHeader.VOICE);
msg.WriteByte((byte)queue.QueueID);
msg.WriteRangedSingle(distanceFactor, 0.0f, 1.0f, 8);
msg.WriteBoolean(isRadio);
queue.Write(msg);
netServer.Send(msg, recipient.Connection, DeliveryMethod.Unreliable);
@@ -69,17 +68,15 @@ namespace Barotrauma.Networking
}
}
private static bool CanReceive(Client sender, Client recipient, out float distanceFactor, out bool isRadio)
private static bool CanReceive(Client sender, Client recipient, out float distanceFactor)
{
if (Screen.Selected != GameMain.GameScreen)
{
distanceFactor = 0.0f;
isRadio = false;
return true;
return true;
}
distanceFactor = 0.0f;
isRadio = false;
//no-one can hear muted players
if (sender.Muted) { return false; }
@@ -112,14 +109,12 @@ namespace Barotrauma.Networking
if (recipientSpectating)
{
isRadio = true;
if (recipient.SpectatePos == null) { return true; }
distanceFactor = MathHelper.Clamp(Vector2.Distance(sender.Character.WorldPosition, recipient.SpectatePos.Value) / senderRadio.Range, 0.0f, 1.0f);
return distanceFactor < 1.0f;
}
else if (recipientRadio != null && recipientRadio.CanReceive(senderRadio))
{
isRadio = true;
distanceFactor = MathHelper.Clamp(Vector2.Distance(sender.Character.WorldPosition, recipient.Character.WorldPosition) / senderRadio.Range, 0.0f, 1.0f);
return true;
}

View File

@@ -2,11 +2,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
namespace Barotrauma
@@ -39,7 +35,27 @@ namespace Barotrauma
}
public int ConnectClients
{
get { return GameMain.Server.ConnectedClients.Count; }
get { return GameMain.Server?.ConnectedClients.Count ?? 0; }
}
public int PendingEntityEvents
{
get { return GameMain.Server?.EntityEventManager?.PendingCreateEventCount ?? 0; }
}
public int EntityEvents
{
get { return GameMain.Server?.EntityEventManager?.EventCount ?? 0; }
}
public int UniqueEntityEvents
{
get { return GameMain.Server?.EntityEventManager?.UniqueEventCount ?? 0; }
}
public int BufferedEntityEvents
{
get { return GameMain.Server?.EntityEventManager?.BufferedEventCount ?? 0; }
}
public double RealTickRate
@@ -166,6 +182,10 @@ namespace Barotrauma
$"Character Count: {CharacterCount}\n" +
$"Clients Count {ConnectClients}\n " +
$"PhysicsBody Count: {PhysicsBodyCount}\n" +
$"Entity Events: {EntityEvents}\n" +
$"Unique Entity Events: {UniqueEntityEvents}\n" +
$"Pending Entity Events: {PendingEntityEvents}\n" +
$"Buffered Entity Events: {BufferedEntityEvents}\n" +
$"Tick Rate: {RealTickRate}\n" +
$"Min Tick Rate: {TickRateLow}\n" +
$"Max Tick Rate: {TickRateHigh}\n" +

View File

@@ -93,6 +93,7 @@ namespace Barotrauma
private static bool hasShutDown = false;
private static void ShutDown()
{
SingleThreadWorker.Instance.Dispose();
if (hasShutDown) { return; }
hasShutDown = true;

View File

@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product>
<Version>1.13.3.1</Version>
<Version>1.12.7.0</Version>
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
<Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName>

View File

@@ -23,70 +23,6 @@
</Holdable>
</Item>
<Item name="fliptestweapon" identifier="fliptestweapon" category="Weapon" cargocontaineridentifier="metalcrate" tags="mediumitem,weapon,gun,gunsmith,provocativetohumanai,mountableweapon" Scale="0.5" impactsoundtag="impact_metal_light">
<PreferredContainer primary="secarmcab" amount="1" spawnprobability="0.2" notcampaign="true" notpvp="true" />
<PreferredContainer secondary="wrecksecarmcab,abandonedsecarmcab,piratesecarmcab" spawnprobability="0.1" />
<PreferredContainer secondary="armcab" />
<Price baseprice="1000" sold="false" minleveldifficulty="30">
<Price storeidentifier="merchantoutpost" multiplier="1.5" />
<Price storeidentifier="merchantcity" multiplier="1.25" />
<Price storeidentifier="merchantresearch" multiplier="1.25" />
<Price storeidentifier="merchantmilitary" sold="true" multiplier="0.9" minavailable="1" />
<Price storeidentifier="merchantmine" multiplier="1.25" />
<Price storeidentifier="merchantarmory" sold="true" multiplier="0.9" minavailable="1" />
</Price>
<Deconstruct time="10">
<Item identifier="steel" />
<Item identifier="titaniumaluminiumalloy" />
</Deconstruct>
<InventoryIcon texture="Content/Items/InventoryIconAtlas.png" sourcerect="896,831,64,64" origin="0.5,0.5" />
<Sprite texture="Content/Items/Weapons/weapons_new.png" sourcerect="0,244,186,65" depth="0.55" origin="0.5,0.5" />
<Body width="170" height="40" density="25" />
<Holdable slots="RightHand+LeftHand" controlpose="true" holdpos="40,-20" aimpos="45,-10" handle1="-33,-15" handle2="26,5" holdangle="-25">
<StatusEffect type="OnActive" target="This" offset="80,20" OffsetCopiesEntityTransform="true">
<ParticleEmitter particle="electricshock" particlespersecond="60" copyentityangle="true" velocitymin="0" velocitymax="100" distancemin="0" distancemax="10" scalemin="0.03" scalemax="0.04" />
</StatusEffect>
</Holdable>
<Wearable slots="Bag" msg="ItemMsgEquipSelect" canbeselected="false" canbepicked="true" pickkey="Select">
<sprite name="Grenade Launcher Worn" texture="Content/Items/Weapons/weapons_new.png" canbehiddenbyotherwearables="false" sourcerect="0,244,186,65" rotation="90" depth="0.6" limb="Torso" depthlimb="LeftArm" scale="0.5" origin="0.5,0.8" />
</Wearable>
<RangedWeapon barrelpos="80,11" reload="0.8" spread="1" unskilledspread="10" combatPriority="75" drawhudwhenequipped="true" crosshairscale="0.2">
<Crosshair texture="Content/Items/Weapons/Crosshairs.png" sourcerect="0,256,256,256" />
<CrosshairPointer texture="Content/Items/Weapons/Crosshairs.png" sourcerect="256,256,256,256" />
<ParticleEmitter particle="muzzleflashchaingun" particleamount="1" velocitymin="0" velocitymax="0" scalemin="0.5" scalemax="0.6" />
<ParticleEmitter particle="explosionsmoke" particleamount="1" velocitymin="0" velocitymax="0" scalemin="0.5" scalemax="0.6" />
<Sound file="Content/Items/Weapons/GrenadeLauncherShot1.ogg" type="OnUse" range="1000" />
<Sound file="Content/Items/Weapons/GrenadeLauncherShot2.ogg" type="OnUse" range="1000" />
<Sound file="Content/Items/Weapons/GrenadeLauncherShot3.ogg" type="OnUse" range="1000" />
<StatusEffect type="OnUse" target="This">
<Explosion range="150.0" force="2" shockwave="false" smoke="false" flames="false" flash="true" sparks="false" underwaterbubble="false" applyfireeffects="false" camerashake="6.0" />
</StatusEffect>
<RequiredItems items="grenade" type="Contained" msg="ItemMsgAmmoRequired" />
<RequiredSkill identifier="weapons" level="60" />
</RangedWeapon>
<!--Holds six grenades at a time-->
<ItemContainer capacity="6" maxstacksize="1" hideitems="false" ShowTotalStackCapacityInContainedStateIndicator="true" containedstateindicatorstyle="bullet" containedspritedepth="0.56">
<Containable items="grenade" hide="true" />
<SlotIcon slotindex="0" texture="Content/UI/StatusMonitorUI.png" sourcerect="448,448,64,64" origin="0.5,0.5" />
<SlotIcon slotindex="1" texture="Content/UI/StatusMonitorUI.png" sourcerect="448,448,64,64" origin="0.5,0.5" />
<SlotIcon slotindex="2" texture="Content/UI/StatusMonitorUI.png" sourcerect="448,448,64,64" origin="0.5,0.5" />
<SlotIcon slotindex="3" texture="Content/UI/StatusMonitorUI.png" sourcerect="448,448,64,64" origin="0.5,0.5" />
<SlotIcon slotindex="4" texture="Content/UI/StatusMonitorUI.png" sourcerect="448,448,64,64" origin="0.5,0.5" />
<SlotIcon slotindex="5" texture="Content/UI/StatusMonitorUI.png" sourcerect="448,448,64,64" origin="0.5,0.5" />
<SlotIcon slotindex="6" texture="Content/UI/StatusMonitorUI.png" sourcerect="320,448,64,64" origin="0.5,0.5" />
<SubContainer capacity="1" maxstacksize="1">
<Containable items="flashlight" hide="false" itempos="22,-1" setactive="true" />
</SubContainer>
</ItemContainer>
<aitarget sightrange="500" soundrange="500" fadeouttime="3" />
<Quality>
<QualityStat stattype="ExplosionRadius" value="0.1" />
<QualityStat stattype="ExplosionDamage" value="0.1" />
</Quality>
<Upgrade gameversion="0.10.0.0" scale="0.5" />
<SkillRequirementHint identifier="weapons" level="60" />
</Item>
<Item name="fliptestlight" identifier="fliptestlighttower" width="176" height="352" texturescale="1.0,1.0" scale="0.5" category="Decorative" subcategory="mining" noninteractable="true">
<sprite texture="Content/Map/Outposts/Art/TunnelWalls.png" sourcerect="849,1697,176,352" depth="0.97" premultiplyalpha="false" origin="0.5,0.5" />
<LightComponent range="160.0" lightcolor="255,234,181,200" IsOn="true" castshadows="false" LightOffset="200,147" allowingameediting="false">

View File

@@ -524,7 +524,8 @@ namespace Barotrauma
{
UnlockAchievement(causeOfDeath.Killer, "killpoison".ToIdentifier());
}
else if (item.Prefab.Tags.Contains("nuclearexplosive"))
else if (item.Prefab.Identifier == "nuclearshell" ||
item.Prefab.Identifier == "nucleardepthcharge")
{
UnlockAchievement(causeOfDeath.Killer, "killnuke".ToIdentifier());
}

View File

@@ -197,18 +197,6 @@ namespace Barotrauma
private readonly List<Body> myBodies;
#if SERVER
/// <summary>
/// How often the server can send messages about a limb targeting some attack target.
/// Mainly relevant for attacks with no cooldown, e.g. fractal guardian's steam cannons which run continuously over time (we can't send events every frame)
/// </summary>
private const double MinSetAttackTargetEventInterval = 0.5;
private IDamageable lastDamageTarget;
private Limb lastTargetLimb;
private Limb lastAttackLimb;
private double lastSetAttackTargetEventTime;
#endif
public LatchOntoAI LatchOntoAI { get; private set; }
public SwarmBehavior SwarmBehavior { get; private set; }
public PetBehavior PetBehavior { get; private set; }
@@ -2691,19 +2679,11 @@ namespace Barotrauma
if (!ActiveAttack.IsRunning)
{
#if SERVER
if (Timing.TotalTime > lastSetAttackTargetEventTime + MinSetAttackTargetEventInterval ||
damageTarget != lastDamageTarget || AttackLimb != lastAttackLimb || targetLimb != lastTargetLimb)
{
GameMain.NetworkMember.CreateEntityEvent(Character, new Character.SetAttackTargetEventData(
AttackLimb,
damageTarget,
targetLimb,
SimPosition));
lastSetAttackTargetEventTime = Timing.TotalTime;
lastDamageTarget = damageTarget;
lastAttackLimb = AttackLimb;
lastTargetLimb = targetLimb;
}
GameMain.NetworkMember.CreateEntityEvent(Character, new Character.SetAttackTargetEventData(
AttackLimb,
damageTarget,
targetLimb,
SimPosition));
#else
Character.PlaySound(CharacterSound.SoundType.Attack, maxInterval: 3);
#endif

View File

@@ -46,7 +46,7 @@ namespace Barotrauma
private float respondToAttackTimer;
private const float RespondToAttackInterval = 1.0f;
private bool wasDead;
private bool wasConscious;
private bool freezeAI;
@@ -201,7 +201,7 @@ namespace Barotrauma
}
if (isIncapacitated) { return; }
wasDead = false;
wasConscious = true;
respondToAttackTimer -= deltaTime;
if (respondToAttackTimer <= 0.0f)
@@ -1256,15 +1256,14 @@ namespace Barotrauma
public override void OnAttacked(Character attacker, AttackResult attackResult)
{
// If the character is incapacitated or dead, respond to the attack anyway to let other nearby characters react to it
// (But if the character is already dead, and was dead before this attack, don't react)
if (Character.IsDead && wasDead) { return; }
if (Character.IsIncapacitated || Character.Stun > 0.0f)
// The attack incapacitated/killed the character: respond immediately to trigger nearby characters because the update loop no longer runs
if (wasConscious && (Character.IsIncapacitated || Character.Stun > 0.0f))
{
RespondToAttack(attacker, attackResult);
wasDead = Character.IsDead;
wasConscious = false;
return;
}
if (Character.IsDead) { return; }
if (attacker == null || Character.IsPlayer)
{
// The player characters need to "respond" to the attack always, because the update loop doesn't run for them.
@@ -1468,10 +1467,10 @@ namespace Barotrauma
otherHumanAI.VisibleHulls.Contains(attacker.CurrentHull) ||
otherCharacter.CanSeeTarget(attacker, seeThroughWindows: true);
if (!isWitnessing)
{
if (Character.IsKnockedDown || otherCharacter.TeamID != Character.TeamID)
{
if (Character.IsDead || Character.IsUnconscious || otherCharacter.TeamID != Character.TeamID)
{
// Knocked down or in different team -> cannot report.
// Dead or in different team -> cannot report.
continue;
}
if (otherHumanAI.objectiveManager.HasOrders())
@@ -1495,14 +1494,6 @@ namespace Barotrauma
continue;
}
}
else if (!otherCharacter.IsSecurity)
{
//witnessed the attack as non-security, trigger security
foreach (Character security in Character.CharacterList.Where(c => c.TeamID == otherCharacter.TeamID))
{
TriggerSecurity(security.AIController as HumanAIController, attacker, DetermineCombatMode(security, cumulativeDamage, isWitnessing));
}
}
float delay = isWitnessing ? GetReactionTime() : Rand.Range(2.0f, 3.0f, Rand.RandSync.Unsynced);
otherHumanAI.AddCombatObjective(combatMode, attacker, delay);
}
@@ -1935,12 +1926,12 @@ namespace Barotrauma
character.IsCriminal = true;
character.IsActingOffensively = true;
}
if (!TriggerSecurity(otherHumanAI, character, combatMode))
if (!TriggerSecurity(otherHumanAI, combatMode))
{
// Else call the others
foreach (Character security in Character.CharacterList.Where(c => c.TeamID == otherCharacter.TeamID).OrderBy(c => Vector2.DistanceSquared(character.WorldPosition, c.WorldPosition)))
{
if (!TriggerSecurity(security.AIController as HumanAIController, character, combatMode))
if (!TriggerSecurity(security.AIController as HumanAIController, combatMode))
{
// Only alert one guard at a time
return;
@@ -1950,25 +1941,25 @@ namespace Barotrauma
}
}
}
private static bool TriggerSecurity(HumanAIController humanAI, Character targetCharacter, AIObjectiveCombat.CombatMode combatMode)
{
if (humanAI == null) { return false; }
if (!humanAI.Character.IsSecurity) { return false; }
if (humanAI.ObjectiveManager.IsCurrentObjective<AIObjectiveCombat>()) { return false; }
humanAI.AddCombatObjective(combatMode, targetCharacter, delay: GetReactionTime(),
onCompleted: () =>
{
//if the target is arrested successfully, reset the damage accumulator
foreach (Character anyCharacter in Character.CharacterList)
{
if (anyCharacter.AIController is HumanAIController anyAI)
bool TriggerSecurity(HumanAIController humanAI, AIObjectiveCombat.CombatMode combatMode)
{
if (humanAI == null) { return false; }
if (!humanAI.Character.IsSecurity) { return false; }
if (humanAI.ObjectiveManager.IsCurrentObjective<AIObjectiveCombat>()) { return false; }
humanAI.AddCombatObjective(combatMode, character, delay: GetReactionTime(),
onCompleted: () =>
{
//if the target is arrested successfully, reset the damage accumulator
foreach (Character anyCharacter in Character.CharacterList)
{
anyAI.structureDamageAccumulator?.Remove(targetCharacter);
if (anyCharacter.AIController is HumanAIController anyAI)
{
anyAI.structureDamageAccumulator?.Remove(character);
}
}
}
});
return true;
});
return true;
}
}
public static void ItemTaken(Item item, Character thief)

View File

@@ -133,7 +133,7 @@ namespace Barotrauma
bool operateExtinguisher = !moveCloser || (dist < extinguisher.Range * 1.2f && character.CanSeeTarget(targetHull));
if (operateExtinguisher)
{
character.CursorPosition = FarseerPhysics.ConvertUnits.ToDisplayUnits(Submarine.GetRelativeSimPositionFromWorldPosition(fs.WorldPosition, character.Submarine, fs.Submarine));
character.CursorPosition = fs.Position;
Vector2 fromCharacterToFireSource = fs.WorldPosition - character.WorldPosition;
character.CursorPosition += VectorExtensions.Forward(extinguisherItem.body.TransformedRotation + (float)Math.Sin(sinTime) / 2, fromCharacterToFireSource.Length() / 2);
if (extinguisherItem.RequireAimToUse)

View File

@@ -173,16 +173,6 @@ namespace Barotrauma
if (character.CanInteractWith(Item, out _, checkLinked: false))
{
waitTimer += deltaTime;
//if we're climbing upwards to the item, ensure the character stays within arm's length of it
//without this, the character can get stuck in a loop where the GoTo objective takes them close enough to the item,
//then the character shifts a bit downwards on the ladder and goes outside interaction range, and the GoTo objective kicks in again
if (character.IsClimbing &&
Item.WorldPosition.Y > character.WorldPosition.Y + FarseerPhysics.ConvertUnits.ToDisplayUnits(character.AnimController.ArmLength))
{
character.AIController.SteeringManager.SteeringManual(deltaTime, Vector2.UnitY);
}
if (waitTimer < WaitTimeBeforeRepair) { return; }
HumanAIController.FaceTarget(Item);

View File

@@ -758,11 +758,6 @@ namespace Barotrauma
public float CoolDownTimer { get; set; }
public float CurrentRandomCoolDown { get; private set; }
public float SecondaryCoolDownTimer { get; set; }
/// <summary>
/// The attack is considered to be running from the moment it starts until the <see cref="AttackTimer"/> reaches the <see cref="Duration"/> of the attack, or until the attack lands successfully.
/// E.g. from the moment the monster decides to lunge itself towards the target until it hits a target or until it completes that lunge.
/// </summary>
public bool IsRunning { get; private set; }
public float AfterAttackTimer { get; set; }

View File

@@ -997,9 +997,6 @@ namespace Barotrauma
public bool IsForceRagdolled;
public bool FollowCursor = true;
/// <summary>
/// Is the character currently dead, unconscious or paralyzed?
/// </summary>
public bool IsIncapacitated
{
get
@@ -1009,9 +1006,6 @@ namespace Barotrauma
}
}
/// <summary>
/// Is the character dead or below 0 vitality and not able to stay conscious?
/// </summary>
public bool IsUnconscious
{
get { return CharacterHealth.IsUnconscious; }
@@ -1679,8 +1673,7 @@ namespace Barotrauma
AnimController.FindHull(setInWater: true);
if (AnimController.CurrentHull != null) { Submarine = AnimController.CurrentHull.Submarine; }
//mass < 35 = husk chimera is the largest vanilla monster that can be contained by default
IsContainable = prefab.ConfigElement.GetAttributeBool(nameof(IsContainable), def: Mass < 35.0f);
IsContainable = prefab.ConfigElement.GetAttributeBool(nameof(IsContainable), def: Mass <= 30.0f);
CharacterList.Add(this);
@@ -2269,10 +2262,7 @@ namespace Barotrauma
{
Vector2 targetMovement = GetTargetMovement();
AnimController.TargetMovement = targetMovement;
if (SelectedItem?.GetComponent<Controller>() is not { ControlCharacterPose: true })
{
AnimController.IgnorePlatforms = AnimController.TargetMovement.Y < -0.1f;
}
AnimController.IgnorePlatforms = AnimController.TargetMovement.Y < -0.1f;
}
if (AnimController is HumanoidAnimController humanAnimController)
@@ -3518,11 +3508,9 @@ namespace Barotrauma
UpdateAttackers(deltaTime);
//use a for loop instead of foreach because talents can unlock other talents via StatusEffectAction (see #17328)
//this way we'll just add them to the end of the list without causing a collection was modified exception
for (int i = 0; i < characterTalents.Count; i++)
foreach (var characterTalent in characterTalents)
{
characterTalents[i].UpdateTalent(deltaTime);
characterTalent.UpdateTalent(deltaTime);
}
if (IsDead) { return; }
@@ -5813,12 +5801,6 @@ namespace Barotrauma
return info.UnlockedTalents.Contains(identifier);
}
public bool IsTalentLocked(Identifier talentIdentifier)
{
if (info == null) { return true; }
return Info.GetSavedStatValue(StatTypes.LockedTalents, talentIdentifier) >= 1;
}
public bool HasUnlockedAllTalents()
{
if (TalentTree.JobTalentTrees.TryGet(Info.Job.Prefab.Identifier, out TalentTree talentTree))

View File

@@ -1022,15 +1022,6 @@ namespace Barotrauma
partial void UpdateProjSpecific(float deltaTime);
#if SERVER
/// <summary>
/// How often the server can send messages about attacks being executed. Note that the timer is per-limb: if one limb executes an attack immediately after another, an network event can still be created.
/// Mainly relevant for attacks with no cooldown, e.g. fractal guardian's steam cannons which run continuously over time (we can't send events every frame)
/// </summary>
private const double MinExecuteAttackEventInterval = 0.5f;
private double lastExecuteAttackEventTime;
#endif
private readonly List<Body> contactBodies = new List<Body>();
/// <summary>
/// Returns true if the attack successfully hit something. If the distance is not given, it will be calculated.
@@ -1151,13 +1142,9 @@ namespace Barotrauma
ExecuteAttack(damageTarget, targetLimb, out attackResult);
}
#if SERVER
if (Timing.TotalTime > lastExecuteAttackEventTime + MinExecuteAttackEventInterval)
{
GameMain.NetworkMember.CreateEntityEvent(character, new Character.ExecuteAttackEventData(
attackLimb: this, targetEntity: damageTarget, targetLimb: targetLimb,
targetSimPos: attackSimPos));
lastExecuteAttackEventTime = Timing.TotalTime;
}
GameMain.NetworkMember.CreateEntityEvent(character, new Character.ExecuteAttackEventData(
attackLimb: this, targetEntity: damageTarget, targetLimb: targetLimb,
targetSimPos: attackSimPos));
#endif
}

View File

@@ -43,14 +43,14 @@ namespace Barotrauma.Abilities
{
foreach (Identifier identifier in option.TalentIdentifiers)
{
if (IsShowCaseTalent(identifier, option) || Character.IsTalentLocked(identifier)) { continue; }
if (IsShowCaseTalent(identifier, option) || TalentTree.IsTalentLocked(identifier, characters)) { continue; }
identifiers.Add(identifier);
}
foreach (var (_, value) in option.ShowCaseTalents)
{
var ids = value.Where(i => !Character.IsTalentLocked(i)).ToImmutableHashSet();
var ids = value.Where(i => !TalentTree.IsTalentLocked(i, characters)).ToImmutableHashSet();
if (ids.Count is 0) { continue; }
identifiers.Add(value.GetRandomUnsynced());

View File

@@ -131,7 +131,7 @@ namespace Barotrauma
if (character.Info.GetTotalTalentPoints() - selectedTalents.Count <= 0) { return false; }
if (!JobTalentTrees.TryGet(character.Info.Job.Prefab.Identifier, out TalentTree talentTree)) { return false; }
if (character.IsTalentLocked(talentIdentifier)) { return false; }
if (IsTalentLocked(talentIdentifier, Character.GetFriendlyCrew(character))) { return false; }
if (character.Info.GetUnlockedTalentsInTree().Contains(talentIdentifier))
{
@@ -163,6 +163,16 @@ namespace Barotrauma
return false;
}
public static bool IsTalentLocked(Identifier talentIdentifier, IEnumerable<Character> characterList)
{
foreach (Character c in characterList)
{
if (c.Info.GetSavedStatValue(StatTypes.LockedTalents, talentIdentifier) >= 1) { return true; }
}
return false;
}
public static List<Identifier> CheckTalentSelection(Character controlledCharacter, IEnumerable<Identifier> selectedTalents)
{
List<Identifier> viableTalents = new List<Identifier>();

View File

@@ -252,7 +252,7 @@ namespace Barotrauma
GameMain.NetworkMember.ShowNetStats = !GameMain.NetworkMember.ShowNetStats;
}));
commands.Add(new Command("spawn|spawncharacter", "spawn [creaturename/jobname] [near/inside/outside/cursor] [team] [add to crew (true/false)] [name]: Spawn a creature at a random spawnpoint (use the second parameter to only select spawnpoints near/inside/outside the submarine). You can also enter the name of a job (e.g. \"Mechanic\") to spawn a character with a specific job and the appropriate equipment.", null,
commands.Add(new Command("spawn|spawncharacter", "spawn [creaturename/jobname] [near/inside/outside/cursor] [team] [add to crew (true/false)]: Spawn a creature at a random spawnpoint (use the second parameter to only select spawnpoints near/inside/outside the submarine). You can also enter the name of a job (e.g. \"Mechanic\") to spawn a character with a specific job and the appropriate equipment.", null,
() =>
{
string[] creatureAndJobNames =
@@ -271,7 +271,7 @@ namespace Barotrauma
};
}, isCheat: true));
commands.Add(new Command("give|giveitem", "give|giveitem [itemname/itemidentifier] [amount] [condition] [quality]: Spawn an item in the inventory of the controlled character",
commands.Add(new Command("give|giveitem", "give|giveitem [itemname/itemidentifier] [amount] [condition]: Spawn an item in the inventory of the controlled character",
(string[] args) =>
{
if (Character.Controlled == null)
@@ -292,12 +292,9 @@ namespace Barotrauma
},
getValidArgs: () =>
{
return new string[][]
return new string[][]
{
GetItemNameOrIdParams().ToArray(),
new string[] { "1" },
new string[] { "100" },
ItemQualityNames.ToArray()
GetItemNameOrIdParams().ToArray()
};
}, isCheat: true));
@@ -314,7 +311,7 @@ namespace Barotrauma
};
}, isCheat: true));
commands.Add(new Command("spawnitem", "spawnitem [itemname/itemidentifier] [cursor/inventory/cargo/random/[name]] [amount] [condition] [quality]: Spawn an item at the position of the cursor, in the inventory of the controlled character, in the inventory of the client with the given name, or at a random spawnpoint if the location parameter is omitted or \"random\".",
commands.Add(new Command("spawnitem", "spawnitem [itemname/itemidentifier] [cursor/inventory/cargo/random/[name]] [amount] [condition]: Spawn an item at the position of the cursor, in the inventory of the controlled character, in the inventory of the client with the given name, or at a random spawnpoint if the location parameter is omitted or \"random\".",
(string[] args) =>
{
TrySpawnItem(args);
@@ -324,10 +321,7 @@ namespace Barotrauma
return new string[][]
{
GetItemNameOrIdParams().ToArray(),
GetSpawnPosParams().ToArray(),
new string[] { "1" },
new string[] { "100" },
ItemQualityNames.ToArray()
GetSpawnPosParams().ToArray()
};
}, isCheat: true));
@@ -1330,7 +1324,6 @@ namespace Barotrauma
}
else
{
if (GameMain.GameSession?.Map is Map map) { NewMessage("Map seed: " + map.Seed); }
NewMessage("Level seed: " + Level.Loaded.Seed);
NewMessage("Level generation params: " + Level.Loaded.GenerationParams.Identifier);
NewMessage("Adjacent locations: " + (Level.Loaded.StartLocation?.Type.Identifier ?? "none".ToIdentifier()) + ", " + (Level.Loaded.StartLocation?.Type.Identifier ?? "none".ToIdentifier()));
@@ -1340,29 +1333,17 @@ namespace Barotrauma
}
},null));
commands.Add(new Command("teleportsub", "teleportsub [start/end/endoutpost/cursor] [submarine_team]: Teleport the submarine to the position of the cursor, or the start or end of the level. The 'endoutpost' argument also automatically docks the sub with the outpost at the end of the level. WARNING: does not take outposts into account, so often leads to physics glitches. Only use for debugging.",
commands.Add(new Command("teleportsub", "teleportsub [start/end/endoutpost/cursor]: Teleport the submarine to the position of the cursor, or the start or end of the level. The 'endoutpost' argument also automatically docks the sub with the outpost at the end of the level. WARNING: does not take outposts into account, so often leads to physics glitches. Only use for debugging.",
onExecute:(string[] args) =>
{
if (Submarine.MainSub == null) { return; }
Submarine submarineToTeleport = Submarine.MainSub;
if (args.Length > 1)
{
foreach (Submarine sub in Submarine.Loaded.Where(s => s.PhysicsBody.BodyType == FarseerPhysics.BodyType.Dynamic))
{
if ((sub.Info.Name + "_" + sub.TeamID) == args[1])
{
submarineToTeleport = sub;
break;
}
}
}
if (args.Length == 0 || args[0].Equals("cursor", StringComparison.OrdinalIgnoreCase))
{
#if SERVER
ThrowError("Cannot teleport the sub to the position of the cursor. Use \"start\" or \"end\", or execute the command as a client.");
#else
submarineToTeleport.SetPosition(Screen.Selected.Cam.ScreenToWorld(PlayerInput.MousePosition));
Submarine.MainSub.SetPosition(Screen.Selected.Cam.ScreenToWorld(PlayerInput.MousePosition));
#endif
}
else if (args[0].Equals("start", StringComparison.OrdinalIgnoreCase))
@@ -1375,9 +1356,9 @@ namespace Barotrauma
Vector2 pos = Level.Loaded.StartPosition;
if (Level.Loaded.StartOutpost != null)
{
pos -= Vector2.UnitY * (submarineToTeleport.Borders.Height + Level.Loaded.StartOutpost.Borders.Height) / 2;
pos -= Vector2.UnitY * (Submarine.MainSub.Borders.Height + Level.Loaded.StartOutpost.Borders.Height) / 2;
}
submarineToTeleport.SetPosition(pos);
Submarine.MainSub.SetPosition(pos);
}
else if (args[0].Equals("end", StringComparison.OrdinalIgnoreCase))
{
@@ -1389,9 +1370,9 @@ namespace Barotrauma
Vector2 pos = Level.Loaded.EndPosition;
if (Level.Loaded.EndOutpost != null)
{
pos -= Vector2.UnitY * (submarineToTeleport.Borders.Height + Level.Loaded.EndOutpost.Borders.Height) / 2;
pos -= Vector2.UnitY * (Submarine.MainSub.Borders.Height + Level.Loaded.EndOutpost.Borders.Height) / 2;
}
submarineToTeleport.SetPosition(pos);
Submarine.MainSub.SetPosition(pos);
}
else if (args[0].Equals("endoutpost", StringComparison.OrdinalIgnoreCase))
{
@@ -1400,8 +1381,8 @@ namespace Barotrauma
NewMessage("Can't teleport the sub to the end outpost (no outpost at the end of the level).", Color.Red);
return;
}
submarineToTeleport.SetPosition(Level.Loaded.EndExitPosition - Vector2.UnitY * submarineToTeleport.Borders.Height);
var submarineDockingPort = DockingPort.List.FirstOrDefault(d => d.Item.Submarine == submarineToTeleport);
Submarine.MainSub.SetPosition(Level.Loaded.EndExitPosition - Vector2.UnitY * Submarine.MainSub.Borders.Height);
var submarineDockingPort = DockingPort.List.FirstOrDefault(d => d.Item.Submarine == Submarine.MainSub);
var outpostDockingPort = DockingPort.List.FirstOrDefault(d => d.Item.Submarine == Level.Loaded.EndOutpost);
if (submarineDockingPort != null && outpostDockingPort != null)
{
@@ -1413,8 +1394,7 @@ namespace Barotrauma
{
return new string[][]
{
new string[] { "start", "end", "endoutpost", "cursor" },
ListAvailableSubmarines()
new string[] { "start", "end", "endoutpost", "cursor" }
};
}, isCheat: true));
@@ -2589,17 +2569,7 @@ namespace Barotrauma
return locationNames.ToArray();
}
private static string[] ListAvailableSubmarines()
{
List<string> submarineNames = new();
foreach (var submarine in Submarine.Loaded.Where(s => s.PhysicsBody.BodyType == FarseerPhysics.BodyType.Dynamic))
{
submarineNames.Add(submarine.Info.Name + "_" + submarine.TeamID);
}
return submarineNames.ToArray();
}
private static bool TryFindTeleportPosition(string locationName, out Vector2 teleportPosition)
{
if (Submarine.MainSub is Submarine mainSub && string.Equals(locationName, "mainsub", StringComparison.InvariantCultureIgnoreCase))
@@ -2982,7 +2952,7 @@ namespace Barotrauma
isHuman = job != null || characterLowerCase == CharacterPrefab.HumanSpeciesName;
}
ParseOptionalArgs(out Vector2 spawnPosition, out WayPoint spawnPoint, out CharacterTeamType? teamType, out bool addToCrew, out string renameCharacter);
ParseOptionalArgs(out Vector2 spawnPosition, out WayPoint spawnPoint, out CharacterTeamType? teamType, out bool addToCrew);
if (usePreConfiguredNPC)
{
@@ -3013,14 +2983,6 @@ namespace Barotrauma
CharacterInfo characterInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobOrJobPrefab: job, variant: variant);
Entity.Spawner.AddCharacterToSpawnQueue(CharacterPrefab.HumanSpeciesName, spawnPosition, characterInfo, onSpawn: newCharacter =>
{
if (renameCharacter != null)
{
if (renameCharacter.Length > 31)
{
renameCharacter = renameCharacter.Substring(0, 32);
}
newCharacter.Info.Name = renameCharacter;
}
SetTeamAndCrew(newCharacter);
newCharacter.GiveJobItems(isPvPMode: GameMain.GameSession?.GameMode is PvPMode, spawnPoint);
newCharacter.GiveIdCardTags(spawnPoint);
@@ -3048,7 +3010,7 @@ namespace Barotrauma
}
}
void ParseOptionalArgs(out Vector2 spawnPosition, out WayPoint spawnPoint, out CharacterTeamType? teamType, out bool addToCrew, out string renameCharacter)
void ParseOptionalArgs(out Vector2 spawnPosition, out WayPoint spawnPoint, out CharacterTeamType? teamType, out bool addToCrew)
{
spawnPosition = Vector2.Zero;
spawnPoint = null;
@@ -3134,12 +3096,6 @@ namespace Barotrauma
ThrowError($"Could not parse the \"add to crew\" argument ({args[argIndex]}). Defaulting to {addToCrew}.");
}
}
argIndex++;
renameCharacter = null;
if (args.Length > argIndex)
{
renameCharacter = args[argIndex];
}
}
}
@@ -3147,7 +3103,6 @@ namespace Barotrauma
{
yield return "cursor";
yield return "inventory";
yield return "cargo";
#if SERVER
if (GameMain.Server != null)
@@ -3188,8 +3143,6 @@ namespace Barotrauma
}
}
private static ImmutableArray<string> ItemQualityNames = ["normal", "good", "excellent", "masterwork"];
private static void TrySpawnItem(string[] args)
{
try
@@ -3250,8 +3203,7 @@ namespace Barotrauma
int amount = 1;
int conditionPrc = 100;
int itemQuality = 0;
if (TryGetSpawnPosParam(out string spawnLocation, out int spawnLocationIndex))
{
switch (spawnLocation)
@@ -3271,7 +3223,7 @@ namespace Barotrauma
break;
default:
var matchingCharacter = FindMatchingCharacter(args.Skip(1).Take(1).ToArray());
if (matchingCharacter != null) { spawnInventory = matchingCharacter.Inventory; }
if (matchingCharacter != null){ spawnInventory = matchingCharacter.Inventory; }
break;
}
@@ -3280,21 +3232,10 @@ namespace Barotrauma
if (!int.TryParse(args[spawnLocationIndex + 1], NumberStyles.Any, CultureInfo.InvariantCulture, out amount)) { amount = 1; }
amount = Math.Min(amount, 100);
}
if (args.Length > spawnLocationIndex + 2)
{
if (!int.TryParse(args[spawnLocationIndex + 2], NumberStyles.Any, CultureInfo.InvariantCulture, out conditionPrc)) { conditionPrc = 100; }
}
if (args.Length > spawnLocationIndex + 3)
{
for (int i = 0; i <= Quality.MaxQuality; i++)
{
if (args[spawnLocationIndex + 3].ToLowerInvariant() == ItemQualityNames[i])
{
itemQuality = i;
}
}
if (!int.TryParse(args[^1], NumberStyles.Any, CultureInfo.InvariantCulture, out conditionPrc)) { conditionPrc = 100; }
}
}
@@ -3316,7 +3257,7 @@ namespace Barotrauma
}
else
{
Entity.Spawner?.AddItemToSpawnQueue(itemPrefab, spawnPos.Value, condition: itemCondition, quality: itemQuality);
Entity.Spawner?.AddItemToSpawnQueue(itemPrefab, spawnPos.Value, condition: itemCondition);
}
}
else if (spawnInventory != null)
@@ -3343,7 +3284,6 @@ namespace Barotrauma
}
item.Condition = item.Health * Math.Clamp(conditionPrc / 100f, 0f, 1f);
item.Quality = itemQuality;
}
}
}

View File

@@ -31,17 +31,12 @@ namespace Barotrauma
Actions = new List<EventAction>();
foreach (var e in element.Elements())
{
if (e.NameAsIdentifier().Equals("statuseffect"))
if (e.Name.ToString().Equals("statuseffect", StringComparison.OrdinalIgnoreCase))
{
DebugConsole.ThrowError($"Error in event prefab \"{scriptedEvent.Prefab.Identifier}\". Status effect configured as a sub action. Please configure status effects as child elements of a StatusEffectAction.",
contentPackage: element.ContentPackage);
continue;
}
else if (e.NameAsIdentifier().Equals(nameof(OnRoundEndAction)))
{
DebugConsole.ThrowError($"Error in event prefab \"{scriptedEvent.Prefab.Identifier}\". {nameof(OnRoundEndAction)} configured as a sub action. Please configure it as an action at the end of the event.",
contentPackage: element.ContentPackage);
}
var action = Instantiate(scriptedEvent, e);
if (action != null) { Actions.Add(action); }
}

View File

@@ -123,7 +123,7 @@ namespace Barotrauma
AddTargetPredicate(
Tags.Traitor,
ScriptedEvent.TargetPredicate.EntityType.Character,
e => e is Character c && (c.IsPlayer || c.IsBot) && c.IsTraitor && !c.IsIncapacitated && CharacterTeamMatches(c));
e => e is Character c && (c.IsPlayer || c.IsBot) && c.IsTraitor && !c.IsIncapacitated);
}
private void TagNonTraitors()
@@ -131,7 +131,7 @@ namespace Barotrauma
AddTargetPredicate(
Tags.NonTraitor,
ScriptedEvent.TargetPredicate.EntityType.Character,
e => e is Character c && (c.IsPlayer || c.IsBot) && !c.IsTraitor && c.IsOnPlayerTeam && !c.IsIncapacitated && CharacterTeamMatches(c));
e => e is Character c && (c.IsPlayer || c.IsBot) && !c.IsTraitor && c.IsOnPlayerTeam && !c.IsIncapacitated);
}
private void TagNonTraitorPlayers()
@@ -139,7 +139,7 @@ namespace Barotrauma
AddTargetPredicate(
Tags.NonTraitorPlayer,
ScriptedEvent.TargetPredicate.EntityType.Character,
e => e is Character c && c.IsPlayer && !c.IsTraitor && c.IsOnPlayerTeam && !c.IsIncapacitated && CharacterTeamMatches(c));
e => e is Character c && c.IsPlayer && !c.IsTraitor && c.IsOnPlayerTeam && !c.IsIncapacitated);
}
private void TagBots(bool playerCrewOnly)
@@ -151,8 +151,7 @@ namespace Barotrauma
e is Character c &&
c.IsBot &&
(!c.IsIncapacitated || !IgnoreIncapacitatedCharacters) &&
(!playerCrewOnly || c.TeamID == CharacterTeamType.Team1) &&
CharacterTeamMatches(c));
(!playerCrewOnly || c.TeamID == CharacterTeamType.Team1));
}
private void TagCrew()
@@ -172,7 +171,7 @@ namespace Barotrauma
private void TagHumansByTag(Identifier tag)
{
AddTarget(Tag, Character.CharacterList.Where(c => c.HumanPrefab != null && c.HumanPrefab.GetTags().Contains(tag) && CharacterTeamMatches(c)));
AddTarget(Tag, Character.CharacterList.Where(c => c.HumanPrefab != null && c.HumanPrefab.GetTags().Contains(tag)));
}
private void TagHumansByJobIdentifier(Identifier jobIdentifier)

View File

@@ -168,9 +168,6 @@ namespace Barotrauma.Items.Components
set { attachedByDefault = value; }
}
#if DEBUG
[Editable]
#endif
[Serialize("0.0,0.0", IsPropertySaveable.No, description: "The position the character holds the item at (in pixels, as an offset from the character's shoulder)."+
" For example, a value of 10,-100 would make the character hold the item 100 pixels below the shoulder and 10 pixels forwards.")]
public Vector2 HoldPos
@@ -180,10 +177,8 @@ namespace Barotrauma.Items.Components
}
//the distance from the holding characters elbow to center of the physics body of the item
protected Vector2 holdPos;
#if DEBUG
[Editable]
#endif
[Serialize("0.0,0.0", IsPropertySaveable.No, description: "The position the character holds the item at when aiming (in pixels, as an offset from the character's shoulder)."+
" Works similarly as HoldPos, except that the position is rotated according to the direction the player is aiming at. For example, a value of 10,-100 would make the character hold the item 100 pixels below the shoulder and 10 pixels forwards when aiming directly to the right.")]
public Vector2 AimPos
@@ -284,9 +279,6 @@ namespace Barotrauma.Items.Components
/// <summary>
/// For setting the handle positions using status effects
/// </summary>
#if DEBUG
[Editable]
#endif
public Vector2 Handle1
{
get { return ConvertUnits.ToDisplayUnits(handlePos[0]); }
@@ -307,9 +299,6 @@ namespace Barotrauma.Items.Components
/// <summary>
/// For setting the handle positions using status effects
/// </summary>
#if DEBUG
[Editable]
#endif
public Vector2 Handle2
{
get { return ConvertUnits.ToDisplayUnits(handlePos[1]); }

View File

@@ -119,7 +119,7 @@ namespace Barotrauma.Items.Components
return OnPicked(picker, pickDroppedStack: true);
}
public bool OnPicked(Character picker, bool pickDroppedStack, bool playSound = true)
public virtual bool OnPicked(Character picker, bool pickDroppedStack)
{
//if the item has multiple Pickable components (e.g. Holdable and Wearable, check that we don't equip it in hands when the item is worn or vice versa)
if (item.GetComponents<Pickable>().Any())
@@ -156,7 +156,7 @@ namespace Barotrauma.Items.Components
ApplyStatusEffects(ActionType.OnPicked, 1.0f, picker);
#if CLIENT
if (!GameMain.Instance.LoadingScreenOpen && playSound && picker == Character.Controlled) { SoundPlayer.PlayUISound(GUISoundType.PickItem); }
if (!GameMain.Instance.LoadingScreenOpen && picker == Character.Controlled) { SoundPlayer.PlayUISound(GUISoundType.PickItem); }
PlaySound(ActionType.OnPicked, picker);
#endif
if (pickDroppedStack)
@@ -164,7 +164,7 @@ namespace Barotrauma.Items.Components
foreach (var droppedItem in droppedStack)
{
if (droppedItem == item) { continue; }
droppedItem.GetComponent<Pickable>().OnPicked(picker, pickDroppedStack: false, playSound: false);
droppedItem.GetComponent<Pickable>().OnPicked(picker, pickDroppedStack: false);
}
}
return true;

View File

@@ -42,9 +42,6 @@ namespace Barotrauma.Items.Components
set;
}
[Serialize(true, IsPropertySaveable.No, description: $"Should this item be removed if the linked character is null?")]
public bool RemoveItemIfCharacterNull { get; set; }
public Character? Character { get; private set; }
public bool DoesBleed => Character?.DoesBleed == true;
@@ -53,8 +50,6 @@ namespace Barotrauma.Items.Components
public LinkedControllerCharacterComponent(Item item, ContentXElement element) : base(item, element)
{
IsActive = true;
#if CLIENT
spriteOverrides = element.Elements()
.Where(static e => e.Name.LocalName.ToLowerInvariant() == "spriteoverride")
@@ -63,16 +58,6 @@ namespace Barotrauma.Items.Components
#endif
}
public override void Update(float deltaTime, Camera cam)
{
base.Update(deltaTime, cam);
if (RemoveItemIfCharacterNull && GameMain.NetworkMember is not { IsClient: true } && (Character == null || Character.Removed))
{
Entity.Spawner?.AddEntityToRemoveQueue(Item);
}
}
public void UpdateLinkedCharacter(Character? character)
{
Character = character;

View File

@@ -525,7 +525,7 @@ namespace Barotrauma.Items.Components
return true;
}
if (containerToSpawnOnSelectedItem.Inventory.AllItems.Any(item => item == spawnedItemOnSelected))
if (containerToSpawnOnSelectedItem.Inventory.AllItems.Any(x => x.Prefab == spawnItemOnSelectedPrefab))
{
return true;
}

View File

@@ -345,8 +345,6 @@ namespace Barotrauma.Items.Components
if (targetItem.AllowDeconstruct && allowRemove)
{
ApplyDeconstructionStatusEffects(targetItem, ActionType.OnDeconstructed, 1f);
//drop all items that are inside the deconstructed item
foreach (ItemContainer ic in targetItem.GetComponents<ItemContainer>())
{
@@ -482,7 +480,6 @@ namespace Barotrauma.Items.Components
// Move items again since the status effect could have spawned additional items in the character inventory
MoveItemsFromCharacterToOutput();
character.Kill(CauseOfDeathType.Unknown, causeOfDeathAffliction: null);
Entity.Spawner?.AddEntityToRemoveQueue(character);
}, 0.1f);
}

View File

@@ -732,7 +732,6 @@ namespace Barotrauma.Items.Components
}
private readonly HashSet<Item> usedIngredients = new HashSet<Item>();
private readonly Dictionary<ItemPrefab, int> ingredientFlexibilityCache = new Dictionary<ItemPrefab, int>();
public bool MissingRequiredRecipe(FabricationRecipe fabricableItem, Character character)
{
@@ -787,24 +786,10 @@ namespace Barotrauma.Items.Components
//maintain a list of used ingredients so we don't end up considering the same item a suitable for multiple required ingredients
usedIngredients.Clear();
// Items are considered more flexible if they can be used in many different requirements
ingredientFlexibilityCache.Clear();
foreach (var prefab in fabricableItem.RequiredItems.SelectMany(static r => r.ItemPrefabs))
{
ingredientFlexibilityCache[prefab] = fabricableItem.RequiredItems.Count(r => r.ItemPrefabs.Contains(prefab));
}
return fabricableItem.RequiredItems
// Match the most restrictive requirements to least restrictive first, while we still have items that we can use
.OrderBy(static r => r.ItemPrefabs.Count())
.ThenByDescending(static requiredItem => requiredItem.Amount)
.All(requiredItem =>
return fabricableItem.RequiredItems.All(requiredItem =>
{
int availableItemsAmount = 0;
foreach (ItemPrefab requiredPrefab in requiredItem.ItemPrefabs
// Fill in the least flexible and more abundant items first, so we don't end up using unique items first
.OrderBy(GetItemFlexibility)
.ThenByDescending(GetAvailableItemsCount))
foreach (ItemPrefab requiredPrefab in requiredItem.ItemPrefabs)
{
if (!availableIngredients.TryGetValue(requiredPrefab.Identifier, out var availableItems)) { continue; }
@@ -826,16 +811,6 @@ namespace Barotrauma.Items.Components
return false;
});
int GetAvailableItemsCount(ItemPrefab itemPrefab)
{
return availableIngredients.TryGetValue(itemPrefab.Identifier, out var list) ? list.Count : 0;
}
int GetItemFlexibility(ItemPrefab itemPrefab)
{
return ingredientFlexibilityCache[itemPrefab];
}
}
private float GetRequiredTime(FabricationRecipe fabricableItem, Character user)

View File

@@ -36,7 +36,7 @@ namespace Barotrauma.Items.Components
}
}
public LocalizedString DisplayName { get; private set; }
public LocalizedString? DisplayName { get; private set; }
private float supplyRatio = 1f;
public float SupplyRatio
@@ -80,7 +80,6 @@ namespace Barotrauma.Items.Components
SupplyRatio = element.GetAttributeFloat("ratio", SupplyRatio);
}
DisplayName = TextManager.Get(name).Fallback(name);
#if CLIENT
CreateGUI();
if (Screen.Selected is not { IsEditor: true })

View File

@@ -252,7 +252,7 @@ namespace Barotrauma.Items.Components
{
PhysicsBody = new PhysicsBody(currentWidth, currentHeight, radius: 0.0f, density: 1.5f, BodyType.Static, Physics.CollisionWall, LevelTrigger.GetCollisionCategories(triggeredBy))
{
UserData = this
UserData = item
};
}
else
@@ -260,7 +260,7 @@ namespace Barotrauma.Items.Components
currentRadius = Math.Max(ConvertUnits.ToSimUnits(Radius * item.Scale), 0.01f);
PhysicsBody = new PhysicsBody(width: 0.0f, height: 0.0f, radius: currentRadius, density: 1.5f, BodyType.Static, Physics.CollisionWall, LevelTrigger.GetCollisionCategories(triggeredBy))
{
UserData = this
UserData = item
};
}

View File

@@ -734,8 +734,8 @@ namespace Barotrauma
[Serialize(false, IsPropertySaveable.No, description: "Hides the condition displayed in the item's tooltip.")]
public bool HideConditionInTooltip { get; set; }
[Serialize("", IsPropertySaveable.No, description: "If set, the item's tooltip displays if the given fabrication recipe has been unlocked or not. The actual unlocking of the recipe should be handled in a status effect.")]
public Identifier[] UnlockedRecipeInToolTip { get; set; }
[Serialize("", IsPropertySaveable.No, description: "If set, displays if the given fabrication recipe has been unlocked or not in the tooltip. The actual unlocking of the recipe should be handled in a status effect.")]
public Identifier UnlockedRecipeInToolTip { get; set; }
//if true and the item has trigger areas defined, characters need to be within the trigger to interact with the item
//if false, trigger areas define areas that can be used to highlight the item

View File

@@ -332,9 +332,17 @@ namespace Barotrauma
void RunStateUnloaded_OnEnter(State<RunState> currentState)
{
Logger.LogMessage("LuaCs unloaded state entered");
Logger.LogResults(PackageManagementService.StopRunningPackages());
DisposeLuaCsConfig();
Logger.LogResults(PackageManagementService.UnloadAllPackages());
if (PackageManagementService.IsAnyPackageRunning())
{
Logger.LogResults(PackageManagementService.StopRunningPackages());
}
if (PackageManagementService.IsAnyPackageLoaded())
{
DisposeLuaCsConfig();
Logger.LogResults(PackageManagementService.UnloadAllPackages());
}
EventService.Reset();
ConfigService.Reset();
@@ -354,7 +362,11 @@ namespace Barotrauma
void RunStateLoadedNoExec_OnEnter(State<RunState> currentState)
{
Logger.LogMessage("LuaCs no execution state entered");
Logger.LogResults(PackageManagementService.StopRunningPackages());
if (PackageManagementService.IsAnyPackageRunning())
{
Logger.LogResults(PackageManagementService.StopRunningPackages());
}
if (!PackageManagementService.IsAnyPackageLoaded())
{

View File

@@ -256,7 +256,6 @@ public sealed class AssemblyLoader : AssemblyLoadContext, IAssemblyLoaderService
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
public Result<Assembly> CompileScriptAssembly([NotNull] string assemblyName,
bool compileWithInternalAccess,
ImmutableArray<SyntaxTree> syntaxTrees,
@@ -349,7 +348,6 @@ public sealed class AssemblyLoader : AssemblyLoadContext, IAssemblyLoaderService
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
public FluentResults.Result<Assembly> LoadAssemblyFromFile(string assemblyFilePath,
ImmutableArray<string> additionalDependencyPaths)
{
@@ -436,8 +434,6 @@ public sealed class AssemblyLoader : AssemblyLoadContext, IAssemblyLoaderService
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
public FluentResults.Result<Assembly> GetAssemblyByName(string assemblyName)
{
if (IsDisposed)
@@ -485,7 +481,6 @@ public sealed class AssemblyLoader : AssemblyLoadContext, IAssemblyLoaderService
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
public FluentResults.Result<ImmutableArray<Type>> GetTypesInAssemblies()
{
if (IsDisposed)
@@ -506,7 +501,6 @@ public sealed class AssemblyLoader : AssemblyLoadContext, IAssemblyLoaderService
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
public IEnumerable<Type> UnsafeGetTypesInAssemblies()
{
if (IsDisposed)
@@ -535,7 +529,6 @@ public sealed class AssemblyLoader : AssemblyLoadContext, IAssemblyLoaderService
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
public Result<Type> GetTypeInAssemblies(string typeName)
{
if (IsDisposed)
@@ -564,12 +557,13 @@ public sealed class AssemblyLoader : AssemblyLoadContext, IAssemblyLoaderService
return; // we don't want to invoke events twice nor cause strong GC handles.
IsDisposed = true;
this.Unload();
this.DisposeInternal();
GC.SuppressFinalize(this);
}
~AssemblyLoader()
{
this.Unload();
this.DisposeInternal();
}
private void OnUnload(AssemblyLoadContext context)
@@ -584,8 +578,8 @@ public sealed class AssemblyLoader : AssemblyLoadContext, IAssemblyLoaderService
Thread.Sleep(1000/Timing.FixedUpdateRate-1);
}
var wf = new WeakReference<IAssemblyLoaderService>(this);
_onUnload?.Invoke(this);
this.DisposeInternal();
}
private void DisposeInternal()
@@ -596,9 +590,6 @@ public sealed class AssemblyLoader : AssemblyLoadContext, IAssemblyLoaderService
base.Unloading -= OnUnload;
this._dependencyResolvers.Clear();
this._loadedAssemblyData.Clear();
GC.Collect(GC.MaxGeneration, GCCollectionMode.Aggressive, true, true);
GC.WaitForFullGCComplete(10);
}
protected override Assembly Load(AssemblyName assemblyName)
@@ -667,7 +658,6 @@ public sealed class AssemblyLoader : AssemblyLoadContext, IAssemblyLoaderService
public readonly ImmutableArray<Type> Types;
public readonly ImmutableDictionary<string, Type> TypesByName;
[MethodImpl(MethodImplOptions.NoOptimization)]
public AssemblyData(Assembly assembly, byte[] assemblyImage)
{
Assembly = assembly ?? throw new ArgumentNullException(nameof(assembly));
@@ -677,7 +667,6 @@ public sealed class AssemblyLoader : AssemblyLoadContext, IAssemblyLoaderService
TypesByName = Types.ToImmutableDictionary(type => type.FullName, type => type);
}
[MethodImpl(MethodImplOptions.NoOptimization)]
public AssemblyData(Assembly assembly, string path)
{
Assembly = assembly ?? throw new ArgumentNullException(nameof(assembly));
@@ -705,7 +694,6 @@ public sealed class AssemblyLoader : AssemblyLoadContext, IAssemblyLoaderService
HashCode = AssemblyName.GetHashCode();
}
[MethodImpl(MethodImplOptions.NoOptimization)]
public AssemblyOrStringKey(string assemblyName)
{
if (assemblyName.IsNullOrWhiteSpace())

View File

@@ -396,7 +396,7 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService,
typeof(ISettingList<ulong>),
typeof(ISettingList<long>),
typeof(ISettingList<float>),
typeof(ISettingList<double>)
typeof(ISettingList<double>),
];
Dictionary<string, Dictionary<string, object>> settingsTable = [];
@@ -420,9 +420,9 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService,
_script.Globals[keyPair.Key] = keyPair.Value;
}
UserData.RegisterType(typeof(ISettingRangeBase<int>));
#if CLIENT
UserData.RegisterType(typeof(ISettingControl));
_script.Globals["SettingControl"] = UserData.CreateStatic(typeof(ISettingControl));
#endif
new LuaConverters(this).RegisterLuaConverters();

View File

@@ -43,9 +43,7 @@ internal class MainMenuPatch : ISystem, IEventScreenSelected
{
if (mainMenuUIAdded) { return; }
var textBlock = new GUITextBlock(
new RectTransform(new Point(300, 30), screen.Frame.RectTransform, Anchor.TopLeft) { AbsoluteOffset = new Point(10, 10) },
"", Color.Red, textAlignment: Alignment.TopLeft)
var textBlock = new GUITextBlock(new RectTransform(new Point(300, 30), screen.Frame.RectTransform, Anchor.TopLeft) { AbsoluteOffset = new Point(10, 10) }, "", Color.Red)
{
IgnoreLayoutGroups = false
};

View File

@@ -23,7 +23,6 @@ public sealed partial class ModConfigFileParserService :
public ModConfigFileParserService(IStorageService storageService)
{
_storageService = storageService;
_storageService.UseCaching = false;
}
#region Dispose

View File

@@ -45,7 +45,6 @@ public sealed class ModConfigService : IModConfigService
#if CLIENT
_stylesParserService = stylesParserService;
#endif
_storageService.UseCaching = false;
}
#region Dispose

View File

@@ -194,7 +194,7 @@ public sealed class PackageManagementService : IPackageManagementService
IService.CheckDisposed(this);
var result = new FluentResults.Result();
var packages2 = packages.OrderBy(pkg => pkg.Name == LuaCsSetup.PackageName ? 0 : 1) // always run lua cs first.
var packages2 = packages.OrderBy(pkg => pkg.Name == "LuaCsForBarotrauma" ? 0 : 1) // always run lua cs first.
.ThenBy(packages.IndexOf)
.ToImmutableArray();
@@ -318,7 +318,7 @@ public sealed class PackageManagementService : IPackageManagementService
// get loading order. Note: packages not in the execution order list will load first.
var loadingOrderedPackages = _loadedPackages
.OrderBy(pkg => pkg.Key.Name == LuaCsSetup.PackageName ? 0 : 1) // always run lua cs first.
.OrderBy(pkg => pkg.Key.Name == "LuaCsForBarotrauma" ? 0 : 1) // always run lua cs first.
.ThenBy(pkg => executionOrder.IndexOf(pkg.Key))
.ToImmutableArray();
var loadOrderByPackage = loadingOrderedPackages.Select(p => p.Key).ToImmutableArray();
@@ -415,9 +415,7 @@ public sealed class PackageManagementService : IPackageManagementService
if (_loadedPackages.IsEmpty || _runningPackages.IsEmpty)
{
#if DEGUG
_logger.LogWarning($"{nameof(StopRunningPackages)}: No packages are currently executing.");
#endif
return FluentResults.Result.Ok();
}

View File

@@ -10,7 +10,6 @@ using System.Runtime.InteropServices;
using System.Runtime.Loader;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml.Serialization;
using Barotrauma.Extensions;
using Barotrauma.IO;
@@ -89,15 +88,7 @@ public class PluginManagementService : IAssemblyManagementService
private ImmutableArray<MetadataReference> _baseMetadataReferences = ImmutableArray<MetadataReference>.Empty;
private ImmutableArray<MetadataReference> _baseMetadataReferencesNonPublicized = ImmutableArray<MetadataReference>.Empty;
private Thread _backgroundGCCleanupThread = null;
private long _backgroundGCWatchdogTicks = 0;
private static readonly int
GC_TASK_COMPLETION_TIMEOUT = 5000,
GC_BACKGND_MAXITERATIONS = 2,
GC_BACKGND_INTERVAL_MILLIS = 200,
GC_BACKGND_GENERATION_WAIT_MILLIS = 100;
private IEnumerable<MetadataReference> BaseMetadataReferences
{
@@ -183,7 +174,6 @@ public class PluginManagementService : IAssemblyManagementService
_pluginInjectorContainer?.Dispose();
_pluginInjectorContainer = null;
ReflectionUtils.ResetCache();
foreach (var loader in _assemblyLoaders)
{
try
@@ -194,6 +184,14 @@ public class PluginManagementService : IAssemblyManagementService
catch (Exception e)
{
_logger?.LogError($"Failed to dispose of {nameof(IAssemblyLoaderService)} for ContentPackage {loader.Key.Name}: \n{e.Message}");
if (loader.Value.Assemblies.Any())
{
foreach (var ass in loader.Value.Assemblies)
{
_logger?.LogWarning($"{nameof(PluginManagementService)}: Fallback manual unsubscription of assemblies: {ass.GetName()}");
ReflectionUtils.RemoveAssemblyFromCache(ass);
}
}
}
}
_assemblyLoaders.Clear();
@@ -224,7 +222,6 @@ public class PluginManagementService : IAssemblyManagementService
private IEventService _pluginEventService;
private Lazy<ILuaPatcher> _pluginLuaPatcherService;
private Func<IConsoleCommandsService> _consoleCommandServiceFactory;
private readonly IConsoleCommandsService _internalConsoleCommandsService;
private ILuaCsInfoProvider _luaCsInfoProvider;
private readonly ConcurrentDictionary<ContentPackage, IAssemblyLoaderService> _assemblyLoaders = new();
private readonly ConcurrentDictionary<Type, ContentPackage> _pluginPackageLookup = new();
@@ -254,21 +251,6 @@ public class PluginManagementService : IAssemblyManagementService
_pluginLuaPatcherService = pluginLuaPatcherService;
_consoleCommandServiceFactory = consoleCommandServiceFactory;
_luaCsInfoProvider = luaCsInfoProvider;
_internalConsoleCommandsService = consoleCommandServiceFactory.Invoke();
RegisterCommands(_internalConsoleCommandsService);
}
private void RegisterCommands(IConsoleCommandsService cmdService)
{
cmdService.RegisterCommand("plugin_forcerungc", "Forces the GC to run", cmds =>
{
_logger.LogMessage("Forcing GC run.");
Task.Factory.StartNew(async () =>
{
await RunGC(true, false);
});
});
}
private ServiceContainer CreatePluginServiceContainer()
@@ -335,13 +317,11 @@ public class PluginManagementService : IAssemblyManagementService
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
public bool TryGetPackageForPlugin<TPlugin>(out ContentPackage ownerPackage)
{
return _pluginPackageLookup.TryGetValue(typeof(TPlugin), out ownerPackage);
}
[MethodImpl(MethodImplOptions.NoInlining)]
public Type GetType(string typeName, bool isByRefType = false, bool includeInterfaces = false,
bool includeDefaultContext = true)
{
@@ -390,7 +370,6 @@ public class PluginManagementService : IAssemblyManagementService
return null;
}
[MethodImpl(MethodImplOptions.NoOptimization)]
public FluentResults.Result ActivatePluginInstances(ImmutableArray<ContentPackage> executionOrder, bool excludeAlreadyRunningPackages = true)
{
if (executionOrder.IsDefaultOrEmpty)
@@ -509,7 +488,6 @@ public class PluginManagementService : IAssemblyManagementService
return results;
// helper
[MethodImpl(MethodImplOptions.NoOptimization)]
FluentResults.Result PluginInitRunner(IAssemblyPlugin plugin, Action<IAssemblyPlugin> action)
{
try
@@ -524,7 +502,7 @@ public class PluginManagementService : IAssemblyManagementService
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
public FluentResults.Result LoadAssemblyResources(ImmutableArray<IAssemblyResourceInfo> resources)
{
if (resources.IsDefaultOrEmpty)
@@ -727,7 +705,7 @@ public class PluginManagementService : IAssemblyManagementService
{
builder.AddRange(BaseMetadataReferencesWithBarotrauma);
foreach (var loaderService in _assemblyLoaders
.Where(asl => !asl.Key.Name.Equals(LuaCsSetup.PackageName, StringComparison.InvariantCultureIgnoreCase))
.Where(asl => !asl.Key.Name.Equals("LuaCsForBarotrauma", StringComparison.InvariantCultureIgnoreCase))
.ToImmutableArray())
{
builder.AddRange(loaderService.Value.AssemblyReferences.Where(ar => ar is not null));
@@ -754,7 +732,7 @@ public class PluginManagementService : IAssemblyManagementService
.Replace(" Barotrauma.Networking.Client.ClientList", " ModUtils.Client.ClientList")
.Replace("ItemPrefab.GetItemPrefab", "ModUtils.ItemPrefab.GetItemPrefab");
}
private IntPtr OnAssemblyLoaderResolvingUnmanaged(Assembly callerAssembly, string targetAssemblyName)
{
Guard.IsNull(callerAssembly, nameof(callerAssembly));
@@ -827,58 +805,21 @@ public class PluginManagementService : IAssemblyManagementService
{
_eventService?.Value?.PublishEvent<IEventAssemblyUnloading>(sub => sub.OnAssemblyUnloading(assembly));
}
_unloadingAssemblyLoaders.Add(loader, loader.OwnerPackage);
}
[MethodImpl(MethodImplOptions.NoOptimization)]
public FluentResults.Result UnloadManagedAssemblies()
{
using var lck = _operationsLock.AcquireWriterLock().ConfigureAwait(false).GetAwaiter().GetResult();
IService.CheckDisposed(this);
if (_assemblyLoaders.Count == 0)
{
return FluentResults.Result.Ok();
}
var results = new FluentResults.Result();
if (!_pluginInstances.IsEmpty)
{
foreach (var instance in _pluginInstances.SelectMany(kvp => kvp.Value))
{
try
{
instance.Dispose();
}
catch (Exception e)
{
results.WithError(new ExceptionalError(e));
continue;
}
}
_pluginInstances.Clear();
}
if (_pluginEventService is not null)
{
_eventService.Value.RemoveDispatcherEventService(_pluginEventService);
try
{
_pluginEventService.Dispose();
}
catch (Exception e)
{
results.WithError(new ExceptionalError(e));
}
_pluginEventService = null;
}
try
{
_pluginInjectorContainer?.Dispose();
}
catch (Exception e)
{
results.WithError(new ExceptionalError(e));
}
_pluginInjectorContainer = null;
results.WithReasons(UnsafeDisposeManagedTypeInstances().Reasons);
ReflectionUtils.ResetCache();
foreach (var loaderService in _assemblyLoaders)
@@ -886,6 +827,7 @@ public class PluginManagementService : IAssemblyManagementService
try
{
loaderService.Value.Dispose();
_unloadingAssemblyLoaders.Add(loaderService.Value, loaderService.Key);
}
catch (Exception e)
{
@@ -895,7 +837,38 @@ public class PluginManagementService : IAssemblyManagementService
_assemblyLoaders.Clear();
_storageService.PurgeCache();
_pluginPackageLookup.Clear();
GC.Collect(GC.MaxGeneration, GCCollectionMode.Aggressive, true);
#if DEBUG
// Print still loaded assembly load ctx after giving some time
CoroutineManager.Invoke(() =>
{
if (!_unloadingAssemblyLoaders.Any())
{
return;
}
StringBuilder sb = new StringBuilder();
sb.AppendLine("The following ContentPackages have not unloaded their assemblies:");
foreach (var kvp in _unloadingAssemblyLoaders.ToImmutableArray())
{
sb.AppendLine($"- '{kvp.Value.Name}'");
}
// Use DebugConsole in case logger is null by the time this executes.
if (_logger is null)
{
DebugConsole.LogError(sb.ToString());
}
else
{
_logger.LogWarning(sb.ToString());
}
}, 3.0f);
#endif
// clear native libraries
if (_loadedNativeLibraries.Any())
@@ -915,109 +888,41 @@ public class PluginManagementService : IAssemblyManagementService
_loadedNativeLibraries.Clear();
}
Task.Factory.StartNew(async () =>
{
await RunGC(true, false);
});
return results;
}
private void SafeLogUnloadingPackages()
private FluentResults.Result UnsafeDisposeManagedTypeInstances()
{
if (!_unloadingAssemblyLoaders.Any())
var results = new FluentResults.Result();
if (!_pluginInstances.IsEmpty)
{
return;
}
StringBuilder sb = new StringBuilder();
sb.AppendLine("The following ContentPackages have not unloaded their assemblies:");
foreach (var kvp in _unloadingAssemblyLoaders.ToImmutableArray())
{
sb.AppendLine($"- '{kvp.Value.Name}'");
}
// Use DebugConsole in case logger is null by the time this executes.
if (_logger is null)
{
DebugConsole.Log(sb.ToString());
}
else
{
_logger.LogWarning(sb.ToString());
}
}
private void GCCleanupTask(TaskCompletionSource<bool> completionSuccess)
{
GC.RegisterForFullGCNotification(1, 1);
try
{
for (int iter = 0; iter < GC_BACKGND_MAXITERATIONS; iter++)
foreach (var instance in _pluginInstances.SelectMany(kvp => kvp.Value))
{
int maxGen = GC.MaxGeneration;
for (int currGen = 0; currGen < maxGen; currGen++)
try
{
GC.Collect(currGen, GCCollectionMode.Forced, false, false); // marking pass
GC.WaitForFullGCComplete(GC_BACKGND_GENERATION_WAIT_MILLIS);
GC.Collect(currGen); // generation cleanup
instance.Dispose();
}
catch (Exception e)
{
results.WithError(new ExceptionalError(e));
continue;
}
Thread.Sleep(GC_BACKGND_INTERVAL_MILLIS);
}
completionSuccess.SetResult(true);
}
catch (ThreadInterruptedException tie)
{
completionSuccess.SetResult(false);
}
catch (Exception e)
{
completionSuccess.SetException(e);
}
finally
{
GC.CancelFullGCNotification();
}
}
private async Task RunGC(bool logResults, bool runOnMainThread)
{
var gcCompletionSuccess = new TaskCompletionSource<bool>();
if (runOnMainThread)
{
GCCleanupTask(gcCompletionSuccess);
if (logResults)
{
SafeLogUnloadingPackages();
}
return;
}
var gcThread = new Thread(() =>
if (_pluginEventService is not null)
{
GCCleanupTask(gcCompletionSuccess);
}) { IsBackground = true };
gcThread.Start();
try
{
await gcCompletionSuccess.Task.WaitAsync(TimeSpan.FromMilliseconds(GC_TASK_COMPLETION_TIMEOUT));
}
catch (TimeoutException te)
{
_logger.LogError($"{nameof(RunGC)}: The GC task thread has timed out.");
gcThread.Interrupt();
gcThread.Join();
}
if (logResults)
{
SafeLogUnloadingPackages();
_eventService.Value.RemoveDispatcherEventService(_pluginEventService);
_pluginEventService = null;
}
_pluginInjectorContainer = null;
_pluginInstances.Clear();
_pluginPackageLookup.Clear();
return results;
}
public Result<Assembly> GetLoadedAssembly(OneOf<AssemblyName, string> assemblyName, in Guid[] excludedContexts)

View File

@@ -814,7 +814,7 @@ namespace Barotrauma
if (outsideCollisionBlocker == null) { return false; }
if (IsRoomToRoom || Submarine == null || open <= 0.0f || linkedTo.Count == 0 || linkedTo[0] is not Hull)
{
SingleThreadWorker.GlobalWorker.AddAction(() =>
SingleThreadWorker.Instance.AddAction(() =>
{
if (outsideCollisionBlocker == null) { return; }
outsideCollisionBlocker.Enabled = false;

View File

@@ -888,22 +888,24 @@ namespace Barotrauma
Oxygen -= OxygenDeteriorationSpeed * deltaTime;
if (FakeFireSources.Count > 0)
SingleThreadWorker.Instance.AddAction(() =>
{
if ((Character.Controlled?.CharacterHealth?.GetAffliction("psychosis")?.Strength ?? 0.0f) <= 0.0f)
if (FakeFireSources.Count > 0)
{
for (int i = FakeFireSources.Count - 1; i >= 0; i--)
if ((Character.Controlled?.CharacterHealth?.GetAffliction("psychosis")?.Strength ?? 0.0f) <= 0.0f)
{
if (FakeFireSources[i].CausedByPsychosis)
for (int i = FakeFireSources.Count - 1; i >= 0; i--)
{
FakeFireSources[i].Remove();
if (FakeFireSources[i].CausedByPsychosis)
{
FakeFireSources[i].Remove();
}
}
}
FireSource.UpdateAll(FakeFireSources, deltaTime);
}
FireSource.UpdateAll(FakeFireSources, deltaTime);
}
FireSource.UpdateAll(FireSources, deltaTime);
FireSource.UpdateAll(FireSources, deltaTime);
});
foreach (Decal decal in decals)
{

View File

@@ -2971,39 +2971,11 @@ namespace Barotrauma
string percentage = string.Format(CultureInfo.InvariantCulture, "{0:P2}", (float)spawnPointsContainingResources / PathPoints.Count);
DebugConsole.NewMessage($"Level resources spawned: {itemCount}\n" +
$" Spawn points containing resources: {spawnPointsContainingResources} ({percentage})\n" +
$" Total value: {GetTotalLevelResourceValue()} mk");
$" Total value: {PathPoints.Sum(p => p.ClusterLocations.Sum(c => c.Resources.Sum(r => r.Prefab.DefaultPrice?.Price ?? 0)))} mk");
if (AbyssResources.Count > 0)
{
DebugConsole.NewMessage($"Abyss resources spawned: {AbyssResources.Sum(a => a.Resources.Count)}\n" +
$" Total value: {GetTotalAbyssResourceValue()} mk");
}
int GetTotalLevelResourceValue()
{
int value = 0;
foreach (var pathPoint in PathPoints)
{
foreach (var clusterLocation in pathPoint.ClusterLocations)
{
foreach (var resource in clusterLocation.Resources)
{
value += resource.Prefab.DefaultPrice?.Price ?? 0;
}
}
}
return value;
}
int GetTotalAbyssResourceValue()
{
int value = 0;
foreach (var clusterLocation in AbyssResources)
{
foreach (var resource in clusterLocation.Resources)
{
value += resource.Prefab.DefaultPrice?.Price ?? 0;
}
}
return value;
$" Total value: {AbyssResources.Sum(c => c.Resources.Sum(r => r.Prefab.DefaultPrice?.Price ?? 0))} mk");
}
#endif
@@ -3232,6 +3204,7 @@ namespace Barotrauma
}
}
/// <param name="rotation">Used by clients to set the rotation for the resources</param>
public List<Item> GenerateMissionResources(ItemPrefab prefab, int requiredAmount, PositionType positionType, IEnumerable<Cave> targetCaves = null)
{
var allValidLocations = GetAllValidClusterLocations();
@@ -5177,7 +5150,6 @@ namespace Barotrauma
renderer.Dispose();
renderer = null;
}
backgroundCreatureManager?.Clear();
#endif
if (LevelObjectManager != null)

View File

@@ -11,7 +11,7 @@ namespace Barotrauma
partial class LevelObject : ISpatialEntity, IDamageable, ISerializableEntity
{
public readonly LevelObjectPrefab Prefab;
public Vector3 Position { get; set; }
public Vector3 Position;
public float NetworkUpdateTimer;

View File

@@ -457,7 +457,7 @@ namespace Barotrauma
if (newObject.NeedsUpdate) { updateableObjects.Add(newObject); }
//add some variance to the Z position to prevent z-fighting
//(based on the x and y position of the object, scaled to be visually insignificant)
newObject.Position += new Vector3(0, 0, (minX + minY) % 100.0f * 0.00001f);
newObject.Position.Z += (minX + minY) % 100.0f * 0.00001f;
int xStart = (int)Math.Floor(minX / GridSize);
int xEnd = (int)Math.Floor(maxX / GridSize);

View File

@@ -348,22 +348,11 @@ namespace Barotrauma
{
price *= 1f - characters.Max(static c => c.GetStatValue(StatTypes.StoreBuyMultiplierAffiliated, includeSaved: false));
price *= 1f - characters.Max(static c => c.Info.GetSavedStatValue(StatTypes.StoreBuyMultiplierAffiliated, Tags.StatIdentifierTargetAll));
price *= 1f - characters.Max(c => GetStatValuesForItem(c, item, StatTypes.StoreBuyMultiplierAffiliated));
price *= 1f - characters.Max(c => item.Tags.Sum(tag => c.Info.GetSavedStatValue(StatTypes.StoreBuyMultiplierAffiliated, tag)));
}
price *= 1f - characters.Max(static c => c.GetStatValue(StatTypes.StoreBuyMultiplier, includeSaved: false));
price *= 1f - characters.Max(c => GetStatValuesForItem(c, item, StatTypes.StoreBuyMultiplier));
price *= 1f - characters.Max(c => item.Tags.Sum(tag => c.Info.GetSavedStatValue(StatTypes.StoreBuyMultiplier, tag)));
}
static float GetStatValuesForItem(Character character, ItemPrefab item, StatTypes statType)
{
float statValueSum = 0.0f;
foreach (Identifier itemTag in item.Tags)
{
statValueSum += character.Info.GetSavedStatValue(statType, itemTag);
}
return statValueSum;
}
// Price should never go below 1 mk
return Math.Max((int)price, 1);
}

View File

@@ -487,19 +487,7 @@ namespace Barotrauma
var portrait = new Sprite(subElement, lazyLoad: true);
if (portrait != null)
{
#if CLIENT
if (!File.Exists(portrait.FilePath))
{
DebugConsole.ThrowError($"Error in location type \"{Identifier}\": cannot find the location portrait \"{portrait.FilePath}\".");
}
else
{
portraitsList.Add(portrait);
}
#elif SERVER
// Add without checking the path, since servers don't parse the file path of the sprite
portraitsList.Add(portrait);
#endif
}
break;
case "store":

View File

@@ -261,7 +261,15 @@ namespace Barotrauma
}
}
AssignEndLocationLevelData(campaign);
foreach (var endLocation in EndLocations)
{
if (endLocation.Type?.ForceLocationName is { IsEmpty: false })
{
endLocation.ForceName(endLocation.Type.ForceLocationName);
}
}
AssignEndLocationLevelData();
//backwards compatibility: if locations go out of bounds (map saved with different generation parameters before width/height were included in the xml)
float maxX = Locations.Select(l => l.MapPosition.X).Max();
@@ -965,43 +973,13 @@ namespace Barotrauma
previousToEndLocation.Connections.Add(endConnection);
endLocation.Connections.Add(endConnection);
AssignEndLocationLevelData(campaign);
AssignEndLocationLevelData();
}
/// <summary>
/// Assigns the correct outpost generation parameters to the end locations. Also checks and ensures that all of them are correctly assigned to the end biome, and have a location type that can be generated in the end biome.
/// Strangely shaped custom maps may sometimes generate in a way that there aren't enough locations in the last biome to assign as the end locations, and we may end up choosing locations in the second-to-last biome instead - let's correct that here.
/// </summary>
/// <param name="campaign"></param>
/// <exception cref="InvalidOperationException"></exception>
private void AssignEndLocationLevelData(CampaignMode campaign)
private void AssignEndLocationLevelData()
{
Biome endBiome = Biome.Prefabs.OrderBy(p => p.UintIdentifier).FirstOrDefault(b => b.IsEndBiome) ?? throw new InvalidOperationException("Could not find an end biome to assign to the end locations.");
LocationType endLocationType =
LocationType.Prefabs
.OrderBy(p => p.UintIdentifier)
.FirstOrDefault(IsSuitableEndLocationType)
?? throw new InvalidOperationException("Could not find an a location type to assign to the end locations.");
bool IsSuitableEndLocationType(LocationType lt)
{
return lt.AreaSettings.Any(s =>
s.Commonness > 0 &&
(s.MatchesBiome(endBiome.Identifier) || s.MatchesZone(generationParams.DifficultyZones)));
}
for (int i = 0; i < endLocations.Count; i++)
{
if (endLocations[i].Biome != endBiome)
{
endLocations[i].Biome = endBiome;
endLocations[i].LevelData = new LevelData(endLocations[i], this, endLocations[i].LevelData.Difficulty);
}
endLocations[i].ChangeType(campaign: campaign, endLocationType);
if (endLocationType.ForceLocationName is { IsEmpty: false })
{
endLocations[i].ForceName(endLocationType.ForceLocationName);
}
endLocations[i].LevelData.ReassignGenerationParams(Seed);
var outpostParams = OutpostGenerationParams.OutpostParams.FirstOrDefault(p => p.ForceToEndLocationIndex == i);
if (outpostParams != null)

View File

@@ -642,6 +642,7 @@ namespace Barotrauma
/// </summary>
public static void UpdateAll(float deltaTime, Camera cam, ParallelOptions parallelOptions)
{
mapEntityUpdateTick++;
#if CLIENT
var sw = new System.Diagnostics.Stopwatch();
sw.Start();
@@ -652,53 +653,61 @@ namespace Barotrauma
var structureList = Structure.WallList.ToList();
List<Gap> gapList = Gap.GapList.ToList();
List<Gap> shuffledGaps = new List<Gap>(gapList?.OrderBy(g => Rand.Int(int.MaxValue)));
// In case if it failed, but why it would fail?
shuffledGaps = shuffledGaps ?? gapList;
// This should never break again... right?
//update gaps in random order, because otherwise in rooms with multiple gaps
//the water/air will always tend to flow through the first gap in the list,
//which may lead to weird behavior like water draining down only through
//one gap in a room even if there are several
int n = gapList.Count;
while (n > 1)
{
n--;
int k = Rand.Int(n + 1);
(gapList[n], gapList[k]) = (gapList[k], gapList[n]);
}
var itemList = Item.ItemList.ToList();
// First phase: parallel updates that have no order dependencies
Parallel.Invoke(parallelOptions,
() =>
{
// basically nothing here is thread-safe so
foreach (var hull in hullList)
{
hull.Update(deltaTime, cam);
}
},
// Structure parallel update
() =>
{
Parallel.ForEach(structureList, parallelOptions, structure =>
{
structure.Update(deltaTime, cam);
});
},
() =>
//update gaps in random order, because otherwise in rooms with multiple gaps
//the water/air will always tend to flow through the first gap in the list,
//which may lead to weird behavior like water draining down only through
//one gap in a room even if there are several
int mapEntityUpdateInterval = Math.Max(MapEntityUpdateInterval, 1);
int poweredUpdateInterval = Math.Max(PoweredUpdateInterval, 1);
// moved waterflow reset here to see if we can reduce at least some time
{
// PLEASE WORK
Parallel.ForEach(shuffledGaps, parallelOptions, gap =>
{
gap.ResetWaterFlowThisFrame();
gap.Update(deltaTime, cam);
});
},
// Powered components update
() =>
{
Powered.UpdatePower(deltaTime);
}
);
if (mapEntityUpdateTick % mapEntityUpdateInterval == 0)
{
float mapEntityDeltaTime = deltaTime * mapEntityUpdateInterval;
SingleThreadWorker.GlobalWorker.RunActions();
Parallel.Invoke(parallelOptions,
() =>
{
Parallel.ForEach(hullList, parallelOptions, hull =>
{
hull.Update(mapEntityDeltaTime, cam);
});
},
() =>
{
Parallel.ForEach(structureList, parallelOptions, structure =>
{
structure.Update(mapEntityDeltaTime, cam);
});
});
}
foreach (Gap gap in gapList)
{
gap.ResetWaterFlowThisFrame();
}
foreach (Gap gap in gapList)
{
gap.Update(deltaTime, cam);
}
if (mapEntityUpdateTick % poweredUpdateInterval == 0)
{
Powered.UpdatePower(deltaTime * poweredUpdateInterval);
}
#if CLIENT
// Hull Cheats need to be executed after Hull update
@@ -714,27 +723,42 @@ namespace Barotrauma
// Item update (Item.Update() is not thread-safe and must be executed on the main thread)
Item.UpdatePendingConditionUpdates(deltaTime);
Item lastUpdatedItem = null;
try
if (mapEntityUpdateTick % mapEntityUpdateInterval == 0)
{
foreach (Item item in itemList)
float itemDeltaTime = deltaTime * mapEntityUpdateInterval;
Item lastUpdatedItem = null;
try
{
lastUpdatedItem = item;
item.Update(deltaTime, cam);
foreach (Item item in itemList)
{
if (LuaCsSetup.Instance.Game.UpdatePriorityItems.Contains(item)) { continue; }
lastUpdatedItem = item;
item.Update(itemDeltaTime, cam);
}
}
catch (InvalidOperationException e)
{
GameAnalyticsManager.AddErrorEventOnce(
"MapEntity.UpdateAll:ItemUpdateInvalidOperation",
GameAnalyticsManager.ErrorSeverity.Critical,
$"Error while updating item {lastUpdatedItem?.Name ?? "null"}: {e.Message}");
throw new InvalidOperationException($"Error while updating item {lastUpdatedItem?.Name ?? "null"}", innerException: e);
}
}
catch (InvalidOperationException e)
foreach (var item in LuaCsSetup.Instance.Game.UpdatePriorityItems)
{
GameAnalyticsManager.AddErrorEventOnce(
"MapEntity.UpdateAll:ItemUpdateInvalidOperation",
GameAnalyticsManager.ErrorSeverity.Critical,
$"Error while updating item {lastUpdatedItem?.Name ?? "null"}: {e.Message}");
throw new InvalidOperationException($"Error while updating item {lastUpdatedItem?.Name ?? "null"}", innerException: e);
if (item.Removed) { continue; }
item.Update(deltaTime, cam);
}
UpdateAllProjSpecific(deltaTime);
Spawner?.Update();
if (mapEntityUpdateTick % mapEntityUpdateInterval == 0)
{
UpdateAllProjSpecific(deltaTime * mapEntityUpdateInterval);
Spawner?.Update();
}
#if CLIENT
sw.Stop();

View File

@@ -275,7 +275,7 @@ namespace Barotrauma
{
CreateStairBodies();
}
else if (Prefab.Body)
else if (HasBody)
{
CreateSections();
UpdateSections();
@@ -346,7 +346,7 @@ namespace Barotrauma
{
Rectangle oldRect = Rect;
base.Rect = value;
if (Prefab.Body)
if (HasBody)
{
CreateSections();
UpdateSections();
@@ -668,7 +668,7 @@ namespace Barotrauma
{
prevSections = Sections.ToArray();
}
if (!Prefab.Body)
if (!HasBody)
{
if (FlippedX && IsHorizontal)
{
@@ -685,7 +685,7 @@ namespace Barotrauma
xsections = 1;
ysections = 1;
}
Sections = new WallSection[Math.Max(xsections, ysections)];
Sections = new WallSection[xsections];
}
else
{
@@ -1635,7 +1635,7 @@ namespace Barotrauma
CreateStairBodies();
}
if (Prefab.Body)
if (HasBody)
{
CreateSections();
UpdateSections();
@@ -1663,7 +1663,7 @@ namespace Barotrauma
CreateStairBodies();
}
if (Prefab.Body)
if (HasBody)
{
CreateSections();
UpdateSections();

View File

@@ -29,31 +29,6 @@ namespace Barotrauma
partial class SubmarineInfo : IDisposable
{
public static HashSet<string> SubmarinePathsWithRemoteStorage { get; set; } = [];
public bool SaveToRemoteStorage
{
get
{
if (FilePath == null) { return false; }
return SubmarinePathsWithRemoteStorage.Contains(FilePath.CleanUpPathCrossPlatform(correctFilenameCase: false));
}
set
{
if (FilePath == null) { return; }
if (value)
{
SubmarinePathsWithRemoteStorage.Add(FilePath.CleanUpPathCrossPlatform(correctFilenameCase: false));
}
else
{
SubmarinePathsWithRemoteStorage.Remove(FilePath.CleanUpPathCrossPlatform(correctFilenameCase: false));
}
}
}
private static List<SubmarineInfo> savedSubmarines = new List<SubmarineInfo>();
public static IEnumerable<SubmarineInfo> SavedSubmarines => savedSubmarines;
@@ -222,8 +197,6 @@ namespace Barotrauma
set;
}
public bool IsFromRemoteStorage;
/// <summary>
/// When enabled, the <see cref="SubmarineElement">XML element is not loaded</see> until it is accessed.
/// </summary>

View File

@@ -6,6 +6,7 @@ using System.Threading.Tasks;
using System.Linq;
using System.Collections.Generic;
using System;
using static Barotrauma.SingleThreadWorker;
#if DEBUG && CLIENT
@@ -261,7 +262,6 @@ namespace Barotrauma
Ragdoll.UpdateAll((float)deltaTime, Cam);
SingleThreadWorker.GlobalWorker.RunActions();
#if CLIENT
sw.Stop();
@@ -279,6 +279,8 @@ namespace Barotrauma
GameMain.PerformanceCounter.AddElapsedTicks("Update:Submarine", sw.ElapsedTicks);
sw.Restart();
#endif
SingleThreadActionStandbySignal.Wait();
try
{
GameMain.World.Step((float)Timing.Step);
@@ -289,6 +291,10 @@ namespace Barotrauma
DebugConsole.ThrowError(errorMsg, e);
GameAnalyticsManager.AddErrorEventOnce("GameScreen.Update:WorldLockedException" + e.Message, GameAnalyticsManager.ErrorSeverity.Critical, errorMsg);
}
finally
{
SingleThreadActionStandbySignal.Release();
}
#if CLIENT
sw.Stop();

View File

@@ -562,19 +562,6 @@ namespace Barotrauma
IgnoredHints.Init(currentConfigDoc.Root.GetChildElement("ignoredhints"));
DebugConsoleMapping.Init(currentConfigDoc.Root.GetChildElement("debugconsolemapping"));
CompletedTutorials.Init(currentConfigDoc.Root.GetChildElement("tutorials"));
var submarineSettings = currentConfigDoc.Root.GetChildElement("submarinesettings");
if (submarineSettings != null)
{
SubmarineInfo.SubmarinePathsWithRemoteStorage.Clear();
foreach (XElement subElement in submarineSettings.Elements("SubmarineWithRemoteStorage"))
{
string path = subElement.GetAttributeString("path", "");
if (!path.IsNullOrEmpty())
{
SubmarineInfo.SubmarinePathsWithRemoteStorage.Add(path);
}
}
}
#endif
}
else
@@ -702,14 +689,7 @@ namespace Barotrauma
XElement tutorialsElement = new XElement("tutorials"); root.Add(tutorialsElement);
CompletedTutorials.Instance.SaveTo(tutorialsElement);
XElement submarineSettings = new XElement("submarinesettings"); root.Add(submarineSettings);
SubmarineInfo.SubmarinePathsWithRemoteStorage.ForEach(path =>
{
submarineSettings.Add(new XElement("SubmarineWithRemoteStorage", new XAttribute("path", path)));
});
XElement keyMappingElement = new XElement("keymapping",
currentConfig.KeyMap.Bindings.Select(kvp
=> new XAttribute(kvp.Key.ToString(), kvp.Value.ToString())));

View File

@@ -1,5 +1,7 @@
using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;
namespace Barotrauma
{
@@ -7,7 +9,14 @@ namespace Barotrauma
{
private ConcurrentQueue<Action> ActionQueue;
public static SingleThreadWorker GlobalWorker = new SingleThreadWorker();
public static SingleThreadWorker Instance = new SingleThreadWorker();
private readonly CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
private readonly SemaphoreSlim actionSignal = new SemaphoreSlim(0);
private readonly Task workerTask;
private bool disposed;
public static readonly SemaphoreSlim SingleThreadActionStandbySignal = new SemaphoreSlim(1);
/// <summary>
/// Initilize a SingleThreadWorker
@@ -16,37 +25,89 @@ namespace Barotrauma
public SingleThreadWorker()
{
ActionQueue = new ConcurrentQueue<Action>();
workerTask = CreateProcessTask(cancellationTokenSource.Token);
}
public void Dispose()
{
if (disposed) { return; }
disposed = true;
cancellationTokenSource.Cancel();
try
{
actionSignal.Release();
workerTask.Wait(2);
}
catch (AggregateException) { }
catch (ObjectDisposedException) { }
cancellationTokenSource.Dispose();
actionSignal.Dispose();
}
private async Task CreateProcessTask(CancellationToken token)
{
while (!token.IsCancellationRequested)
{
bool lockTaken = false;
try
{
await actionSignal.WaitAsync(100, token);
SingleThreadActionStandbySignal.Wait(CancellationToken.None);
lockTaken = true;
RunActions();
}
catch (OperationCanceledException)
{
break;
}
finally
{
if (lockTaken)
{
SingleThreadActionStandbySignal.Release();
}
}
}
}
/// <summary>
/// Add a pending action in a STW queue
/// Add a pending action in a STW queue.
/// DO NOT ABUSE IT OR IT WILL SLOW DOWN MAIN THREAD!!!!
/// </summary>
/// <param name="action"></param>
public void AddAction(Action action)
{
if (disposed || action == null) { return; }
// enqueue and let background task handle the rest
ActionQueue.Enqueue(action);
if (actionSignal.CurrentCount == 0)
{
actionSignal.Release();
}
}
/// <summary>
/// Run all pending actions in the STW queue
/// </summary>
[STAThread]
public void RunActions()
private void RunActions()
{
while (ActionQueue.TryDequeue(out Action action))
{
try
{
action();
action?.Invoke();
}
catch (Exception e)
{
// Just try-catch and do nothing but print errorlogs. We cannot afford crashing the game.
ConsoleColor originalForeground = Console.ForegroundColor;
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine($"WARNING: Error occurred when running Single Thread Actions. " +
Console.WriteLine($"WARNING: Error occurred when running Single Thread Actions." +
$"If the server didn't crash or stop responding then this should be fine \n{e}");
Console.ForegroundColor = Console.ForegroundColor;
Console.ForegroundColor = originalForeground;
}
}
}

View File

@@ -1786,7 +1786,7 @@ namespace Barotrauma
offset *= item.Scale;
if (item.FlippedX) { offset.X *= -1; }
if (item.FlippedY) { offset.Y *= -1; }
offset = Vector2.Transform(offset, Matrix.CreateRotationZ(item.body?.Rotation ?? -item.RotationRad));
offset = Vector2.Transform(offset, Matrix.CreateRotationZ(-item.RotationRad));
}
}

View File

@@ -1,105 +0,0 @@
#nullable enable
using Barotrauma.IO;
using Microsoft.Xna.Framework;
using Steamworks;
using System;
using System.Diagnostics.CodeAnalysis;
namespace Barotrauma.Steam;
internal static partial class RemoteStorageHelper
{
public static readonly Color SteamColor = Color.DodgerBlue;
public static readonly string DebugPrefix = $"‖color:{SteamColor.ToStringHex()}‖[Remote Storage]‖end‖";
/// <summary>Attempts to read a file from remote storage into a byte array.</summary>
/// <param name="remoteFile">The remote file to read from.</param>
/// <param name="bytes">The bytes read from the remote file. Returns <see langword="null"/> if the operation failed.</param>
/// <returns>
/// <see langword="true"/> if the operation was successful.<br/>
/// <see langword="false"/> if the operation failed.
/// </returns>
public static bool TryRead(this SteamRemoteStorage.RemoteFile remoteFile, [NotNullWhen(returnValue: true)] out byte[]? bytes, bool logError = true)
{
bytes = SteamRemoteStorage.FileRead(remoteFile.Filename);
bool success = bytes != null;
if (logError && !success)
{
DebugConsole.ThrowError($"{DebugPrefix} Failed to read file \"{remoteFile.Filename}\" from remote storage: operation failed.");
}
return success;
}
/// <summary>Attempts to write a file to remote storage.</summary>
/// <param name="localPath">The path of the local file to read from.</param>
/// <param name="saveAs">The name of the remote file to write to. If <see langword="null"/>, the file name of <paramref name="localPath"/> is used.</param>
/// <param name="allowOverwrite">If <see langword="true"/>, overwriting existing remote files is allowed.</param>
/// <returns>
/// <see langword="true"/> if the operation was successful.<br/>
/// <see langword="false"/> if the operation failed.
/// </returns>
public static bool TryWrite(string localPath, string? saveAs = null, bool allowOverwrite = false, bool logError = true)
{
string fileName = saveAs ?? Path.GetFileName(localPath);
if (!allowOverwrite && SteamRemoteStorage.FileExists(fileName))
{
if (logError)
{
DebugConsole.ThrowError($"{DebugPrefix} Failed to write file \"{fileName}\" to remote storage: file already exists.");
}
return false;
}
byte[] data;
try
{
data = File.ReadAllBytes(localPath);
}
catch (Exception exception)
{
if (logError)
{
DebugConsole.ThrowError($"{DebugPrefix} Failed to read file \"{fileName}\" while writing to remote storage: {exception}");
}
return false;
}
bool success = SteamRemoteStorage.FileWrite(fileName, data);
if (logError && !success)
{
DebugConsole.ThrowError($"{DebugPrefix} Failed to write file \"{fileName}\" to remote storage: operation failed.");
}
return success;
}
/// <summary>Attempts to delete a file from remote storage.</summary>
/// <param name="fileName">The name of the remote file to delete.</param>
/// <returns>
/// <see langword="true"/> if the operation was successful.<br/>
/// <see langword="false"/> if the operation failed.
/// </returns>
public static bool TryDelete(string fileName, bool logError = true)
{
bool success = SteamRemoteStorage.FileDelete(fileName);
if (logError && !success)
{
DebugConsole.ThrowError($"{DebugPrefix} Failed to delete file \"{fileName}\" from remote storage: operation failed.");
}
return success;
}
/// <summary>Checks if a file is stored remotely.</summary>
/// <param name="fileName">The name of the remote file to check.</param>
/// <returns>
/// <see langword="true"/> if the file is stored.<br/>
/// <see langword="false"/> if the file is not stored or the operation failed.
/// </returns>
public static bool IsStored(string fileName) => SteamRemoteStorage.FileExists(fileName);
}

View File

@@ -1,58 +1,4 @@
-------------------------------------------------------------------------------------------------------------------------------------------------
v1.13.3.1 (Summer Update 2026)
-------------------------------------------------------------------------------------------------------------------------------------------------
Submarine reworks:
- Humpback, Orca 2, Azimuth, Typhon, and Herja have received their visual and gameplay reworks.
- The command room of the Orca 2 is now located at the center of the submarine.
- Typhon now comes with valves and pipe weakpoints.
- Herja has been upgraded with a power distributor, befitting its high-tech theme.
Changes and additions:
- Added an option to back up your custom submarines in the Steam Cloud. Can be enabled per-submarine using a checkbox in the sub editor's save dialog.
- Added quality parameter to the give/spawnitem console commands. Allows spawning in items with non-default quality.
- The teleportsub console command has a parameter for choosing which submarine to teleport.
- The spawncharacter console command has a parameter for renaming the spawned character.
- The showseed console command displays the map seed too if used in campaign mode.
Multiplayer:
- Fixed monster attacks that run over time (e.g. when fractal guardians fire the steam cannon) causing an excessive amount of network usage in multiplayer.
- Fixed an exploit that allowed modified clients to cause other clients to eventually get out of sync and disconnect.
- Fixed inability to drag and drop stacks of items to other players in multiplayer.
- Fixed submarine voting not working in campaign mode.
Miscellaneous fixes:
- Fixed security (or anyone else) not reacting to attacking stunned/incapacitated characters.
- Fixed the item pickup sound playing multiple times, for every item in a stack you're picking up.
- Fixed the item dropping sound playing twice when dropping an item.
- Fixed being unable to fabricate certain items with specific combinations of materials. Happened in some cases where the recipe accepted multiple different materials as ingredients: the fabricator would got through the requirements in order, and always take the first available items without considering that the item could've been necessary for another, more strict requirement.
- Followup to the "infinite explosion" fix in Summer Update 2025: the previous fix only applied to oxygen tank shelves, but it turned out oxygen generators could also cause the same kind of "explosion loop" where tanks keep exploding and getting refilled by the oxygen generator.
- Fixed "inspirational leader" talent not giving bonus XP like the description says it should.
- Fixed characters being able to drop off platforms while using a periscope (inconsistent with other movement inputs being disabled while on a periscope).
- Fixed bots being unable to extinguish fires in connected subs (e.g. in Remora's drone).
- Fixed parts of the CPR button not being clickable on the health HUD on certain resolutions (was getting blocked by the limb indicators).
- Fixed nuclear shells fabricated with the cheaper recipe variant not giving the "I am become death" achievement.
- Fixed gravity spheres (or more generally, any items with a triggercomponent) taking damage when you cut their trigger area with a plasma cutter, rather than the actual collider of the item.
- Fixed equip buttons being clickable despite the slot being hidden. Meant that when you had equipped an item in your hand, you could click an invisible button at the left side of the inventory where the hand slots would appear.
- Fixed turrets not showing the ammo on the HUD if the ammo is inside the turret itself, rather than a linked loader.
- If one of the unique hireable characters (e.g. Ignatius May, Aunt Doris) dies in the outpost before you hire them, they can no longer appear elsewhere or be hired.
- Fixed cargo scooter lights working, but not draining the battery, when the battery is in another slot than the battery slot.
- Fixed custom interaction messages set on items in the sub editor no longer appearing in-game.
- Allow combining defense bot ammo boxes the same way as other ammo boxes and magazines (merging their ammo together).
- Fixed the character deconstruction bag staying in the deconstructor if you do a level transition while a character is inside the deconstructor.
- Fixed items duplicating if a character gets deconstructed without dying first (possible e.g. by taking advantage of the Miracle Worker talent).
- Fixed crafting blueprint tooltips not showing whether the recipe has been unlocked or not.
- Fixed valves potentially getting stuck in a non-interactable state if the round ends immediately after one's been toggled.
Modding:
- Fixed "LockedTalents" PermanentStat locking the talent for everyone (not used in any vanilla talent).
- Clients are allowed to use colored text in their chat messages when they have the "chat spam immunity" permission. Colored text was disabled in client-sent chat messages in the previous update due to some ways in which it can be abused, but turns out there were some users relying on this functionality.
- Fixed OnDeconstructed status effect triggering when the item is not deconstructed in some cases (e.g. researching unidentified genetic material without stabilozine).
- Fixed the special locations at the end of the campaign map generating incorrectly on very short maps.
- Fixed status effects using OffsetCopiesEntityTransform not taking physics body rotation into account.
- Fixed TagAction's Team setting being ignored when tagging characters in certain ways (e.g. traitors, non-traitors, bots, human prefab tags).
-------------------------------------------------------------------------------------------------------------------------------------------------
v1.12.7.0
-------------------------------------------------------------------------------------------------------------------------------------------------

View File

@@ -21,7 +21,7 @@ You need a version of Visual Studio that supports C# 10 to compile game. If you
When installing on Windows, make sure you select ".NET desktop development" during the install process to make sure you have the required features to work with Barotrauma.
#### Linux
You will need to install the .NET 8 SDK according to the instructions laid out on Microsoft's docs: https://docs.microsoft.com/en-us/dotnet/core/install/linux
You will need to install the .NET 6 SDK according to the instructions laid out on Microsoft's docs: https://docs.microsoft.com/en-us/dotnet/core/install/linux
To edit the source code, we recommend using [Visual Studio Code](https://code.visualstudio.com/) with [Microsoft's C# extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode.csharp).

View File

@@ -114,8 +114,8 @@ namespace Barotrauma
public static void ResetCache()
{
CachedNonAbstractTypes.Clear();
TypeSearchCache.Clear();
CachedNonAbstractTypes.TryAdd(typeof(ReflectionUtils).Assembly, typeof(ReflectionUtils).Assembly.GetTypes().Where(t => !t.IsAbstract).ToImmutableArray());
TypeSearchCache.Clear();
}
public static Type? GetType(string nameWithNamespace)