(61d00a474) v0.9.7.1

This commit is contained in:
Regalis
2020-03-04 13:04:10 +01:00
parent 3c50efa5c9
commit 3c09ebe02f
5086 changed files with 786063 additions and 295871 deletions

11
.gitignore vendored
View File

@@ -11,12 +11,23 @@ bld/
[Oo]bj/
[Dd]ebug*/
[Rr]elease*/
*.o
# Misc vs crap
*.v12.suo
*.suo
*.csproj.user
*.shproj.user
*.vcxproj.user
# Platform-specific webm_mem_playback files
Libraries/webm_mem_playback/libvpx_x64_linux/
Libraries/webm_mem_playback/libvpx_x64_vs15/
Libraries/webm_mem_playback/libvpx_x86_vs15/
Libraries/webm_mem_playback/libwebm_x64_linux/
Libraries/webm_mem_playback/libwebm_x64_vs19/
Libraries/webm_mem_playback/libwebm_x86_vs19/
Libraries/webm_mem_playback/opus_x64_linux/
#performance reports & sessions
*.vsp

View File

@@ -1,35 +0,0 @@
#!/bin/bash
# MonoKickstart Shell Script
# Written by Ethan "flibitijibibo" Lee
# Move to script's directory
cd "`dirname "$0"`"
# Get the system architecture
UNAME=`uname`
ARCH=`uname -m`
# MonoKickstart picks the right libfolder, so just execute the right binary.
if [ "$UNAME" == "Darwin" ]; then
# ... Except on OSX.
export DYLD_LIBRARY_PATH=$DYLD_LIBRARY_PATH:./osx/
# El Capitan is a total idiot and wipes this variable out, making the
# Steam overlay disappear. This sidesteps "System Integrity Protection"
# and resets the variable with Valve's own variable (they provided this
# fix by the way, thanks Valve!). Note that you will need to update your
# launch configuration to the script location, NOT just the app location
# (i.e. Kick.app/Contents/MacOS/Kick, not just Kick.app).
# -flibit
if [ "$STEAM_DYLD_INSERT_LIBRARIES" != "" ] && [ "$DYLD_INSERT_LIBRARIES" == "" ]; then
export DYLD_INSERT_LIBRARIES="$STEAM_DYLD_INSERT_LIBRARIES"
fi
./Barotrauma.bin.osx $@
else
if [ "$ARCH" == "x86_64" ]; then
./Barotrauma.bin.x86_64 $@
else
./Barotrauma.bin.x86 $@
fi
fi

View File

@@ -1,242 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
<HasSharedItems>true</HasSharedItems>
<SharedGUID>51aff563-4982-474d-a92f-50b06db05b3d</SharedGUID>
</PropertyGroup>
<PropertyGroup Label="Configuration">
<Import_RootNamespace>ClientCode</Import_RootNamespace>
</PropertyGroup>
<ItemGroup>
<Compile Include="$(MSBuildThisFileDirectory)Source\Camera.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\AICharacter.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\AI\AIController.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\AI\AITarget.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\AI\EnemyAIController.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\AI\HumanAIController.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\Animation\Ragdoll.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\Attack.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\Character.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\Health\CharacterHealth.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\CharacterHUD.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\CharacterInfo.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\CharacterNetworking.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\CharacterSound.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\Health\AfflictionHusk.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\Health\AfflictionPsychosis.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\Health\DamageModifier.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\HUDProgressBar.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\Jobs\JobPrefab.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\Limb.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\DebugConsole.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\EventInput\EventInput.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\EventInput\KeyboardDispatcher.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Events\EventManager.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Events\Missions\CombatMission.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Events\Missions\Mission.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Events\Missions\MissionMode.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Events\Missions\MissionPrefab.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Fonts\ScalableFont.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\GameMain.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\GameSession\CrewManager.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\GameSession\GameMode.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\GameSession\GameModes\CampaignMode.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\GameSession\GameModes\MultiPlayerCampaign.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\GameSession\GameModes\SinglePlayerCampaign.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\GameSession\GameModes\Tutorials\BasicTutorial.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\GameSession\GameModes\Tutorials\CaptainTutorial.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\GameSession\GameModes\Tutorials\DoctorTutorial.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\GameSession\GameModes\Tutorials\EditorTutorial.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\GameSession\GameModes\Tutorials\EngineerTutorial.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\GameSession\GameModes\Tutorials\MechanicTutorial.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\GameSession\GameModes\Tutorials\OfficerTutorial.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\GameSession\GameModes\Tutorials\ScenarioTutorial.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\GameSession\GameModes\Tutorials\Tutorial.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\GameSession\GameModes\Tutorials\TutorialMode.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\GameSession\GameSession.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\GameSession\RoundSummary.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\GameSettings.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\GUI\ChatBox.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\GUI\ComponentStyle.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\GUI\Graph.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\GUI\GUI.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\GUI\GUIButton.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\GUI\GUICanvas.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\GUI\GUIComponent.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\GUI\GUICustomComponent.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\GUI\GUIDropDown.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\GUI\GUIFrame.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\GUI\GUIImage.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\GUI\GUILayoutGroup.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\GUI\GUIListBox.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\GUI\GUIMessage.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\GUI\GUIMessageBox.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\GUI\GUINumberInput.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\GUI\GUIProgressBar.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\GUI\GUIRadioButtonGroup.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\GUI\GUIScrollBar.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\GUI\GUIStyle.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\GUI\GUITextBlock.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\GUI\GUITextBox.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\GUI\GUITickBox.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\GUI\HUDLayoutSettings.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\GUI\LoadingScreen.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\GUI\ParamsEditor.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\GUI\RectTransform.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\GUI\ShapeExtensions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\GUI\UISprite.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\GUI\VideoPlayer.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\GUI\Widget.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Items\CharacterInventory.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Items\Components\Door.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Items\Components\ElectricalDischarger.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Items\Components\ItemComponent.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Items\Components\ItemContainer.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Items\Components\ItemLabel.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Items\Components\LevelResource.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Items\Components\LightComponent.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Items\Components\Machines\Controller.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Items\Components\Machines\Deconstructor.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Items\Components\Machines\Engine.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Items\Components\Machines\Fabricator.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Items\Components\Machines\MiniMap.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Items\Components\Machines\Pump.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Items\Components\Machines\Reactor.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Items\Components\Machines\Sonar.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Items\Components\Machines\Steering.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Items\Components\Power\PowerContainer.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Items\Components\Power\Powered.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Items\Components\Power\PowerTransfer.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Items\Components\Repairable.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Items\Components\RepairTool.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Items\Components\Signal\Connection.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Items\Components\Signal\ConnectionPanel.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Items\Components\Signal\CustomInterface.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Items\Components\Signal\MotionSensor.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Items\Components\Signal\WifiComponent.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Items\Components\Signal\Wire.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Items\Components\StatusHUD.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Items\Components\Turret.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Items\DockingPort.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Items\Inventory.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Items\Item.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Items\ItemInventory.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Items\ItemPrefab.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Items\Rope.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Map\Explosion.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Map\FireSource.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Map\Gap.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Map\Hull.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Map\ItemAssemblyPrefab.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Map\Levels\BackgroundCreatures\BackgroundCreature.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Map\Levels\BackgroundCreatures\BackgroundCreatureManager.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Map\Levels\BackgroundCreatures\BackgroundCreaturePrefab.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Map\Levels\CaveGenerator.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Map\Levels\Level.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Map\Levels\LevelObjects\LevelObject.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Map\Levels\LevelObjects\LevelObjectManager.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Map\Levels\LevelObjects\LevelObjectPrefab.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Map\Levels\LevelObjects\LevelTrigger.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Map\Levels\LevelRenderer.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Map\Levels\LevelWall.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Map\Levels\Ruins\RuinGenerator.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Map\Levels\WaterRenderer.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Map\Lights\ConvexHull.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Map\Lights\LightManager.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Map\Lights\LightSource.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Map\LinkedSubmarine.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Map\MapEntity.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Map\MapEntityPrefab.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Map\Map\Location.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Map\Map\Map.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Map\Structure.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Map\StructurePrefab.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Map\Submarine.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Map\SubmarineBody.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Map\WayPoint.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Media\Video.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Networking\BanList.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Networking\ChatMessage.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Networking\Client.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Networking\EntitySpawner.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Networking\FileTransfer\FileReceiver.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Networking\GameClient.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Networking\KarmaManager.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Networking\NetEntityEvent\ClientEntityEventManager.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Networking\NetEntityEvent\NetEntityEvent.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Networking\NetStats.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Networking\OrderChatMessage.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Networking\Primitives\Peers\ClientPeer.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Networking\Primitives\Peers\LidgrenClientPeer.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Networking\Primitives\Peers\SteamP2PClientPeer.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Networking\Primitives\Peers\SteamP2POwnerPeer.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Networking\RespawnManager.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Networking\ServerInfo.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Networking\ServerLog.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Networking\ServerSettings.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Networking\SteamManager.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Networking\Voip\VoipCapture.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Networking\Voip\VoipClient.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Networking\Voip\VoipConfig.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Networking\Voting.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Networking\WhiteList.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Particles\Decal.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Particles\DecalManager.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Particles\DecalPrefab.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Particles\Particle.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Particles\ParticleEmitter.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Particles\ParticleManager.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Particles\ParticlePrefab.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Physics\PhysicsBody.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\PlayerInput.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Program.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Screens\BlurEffect.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Screens\CampaignSetupUI.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Screens\CampaignUI.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Screens\CharacterEditor\CharacterEditorScreen.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Screens\CharacterEditor\Wizard.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Screens\CreditsPlayer.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Screens\GameScreen.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Screens\LevelEditorScreen.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Screens\LobbyScreen.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Screens\MainMenuScreen.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Screens\NetLobbyScreen.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Screens\ParticleEditorScreen.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Screens\Screen.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Screens\ServerListScreen.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Screens\SpriteEditorScreen.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Screens\SteamWorkshopScreen.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Screens\SubEditorScreen.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Serialization\SerializableEntityEditor.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Sounds\OggSound.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Sounds\OpenAL\Al.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Sounds\OpenAL\Alc.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Sounds\Sound.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Sounds\SoundChannel.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Sounds\SoundFilters.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Sounds\SoundManager.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Sounds\SoundPlayer.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Sounds\VideoSound.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Sounds\VoipSound.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Sprite\ConditionalSprite.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Sprite\DecorativeSprite.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Sprite\DeformableSprite.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Sprite\DeformAnimations\CustomDeformation.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Sprite\DeformAnimations\Inflate.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Sprite\DeformAnimations\JointBendDeformation.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Sprite\DeformAnimations\NoiseDeformation.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Sprite\DeformAnimations\PositionalDeformation.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Sprite\DeformAnimations\SpriteDeformation.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Sprite\Sprite.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Sprite\SpriteSheet.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\StatusEffects\StatusEffect.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Traitors\TraitorMissionPrefab.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Utils\CrossThread.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Utils\LocalizationCSVtoXML.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Utils\MathUtils.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Utils\OpenFileDialog.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Utils\TextureLoader.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Utils\ToolBox.cs" />
</ItemGroup>
</Project>

View File

@@ -1,14 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Label="Globals">
<ProjectGuid>51aff563-4982-474d-a92f-50b06db05b3d</ProjectGuid>
<MinimumVisualStudioVersion>14.0</MinimumVisualStudioVersion>
<ReleaseVersion>0.9.0.0</ReleaseVersion>
</PropertyGroup>
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.Default.props" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.props" />
<PropertyGroup />
<Import Project="ClientCode.projitems" Label="Shared" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.CSharp.targets" />
</Project>

View File

@@ -0,0 +1,409 @@
using Barotrauma.Networking;
using Lidgren.Network;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using System;
namespace Barotrauma
{
public class Camera
{
public static bool FollowSub = true;
private float? defaultZoom;
public float DefaultZoom
{
get { return defaultZoom ?? (GameMain.Config == null || GameMain.Config.EnableMouseLook ? 1.3f : 1.0f); }
set
{
defaultZoom = MathHelper.Clamp(value, 0.5f, 2.0f);
}
}
private float zoomSmoothness = 8.0f;
public float ZoomSmoothness
{
get { return zoomSmoothness; }
set { zoomSmoothness = Math.Max(value, 0.01f); }
}
private float moveSmoothness = 8.0f;
public float MoveSmoothness
{
get { return moveSmoothness; }
set { moveSmoothness = Math.Max(value, 0.01f); }
}
private float minZoom = 0.1f;
public float MinZoom
{
get { return minZoom;}
set { minZoom = MathHelper.Clamp(value, 0.01f, 10.0f); }
}
private float maxZoom = 2.0f;
public float MaxZoom
{
get { return maxZoom; }
set { maxZoom = MathHelper.Clamp(value, 1.0f, 10.0f); }
}
private float zoom;
private float offsetAmount;
private Matrix transform, shaderTransform, viewMatrix;
private Vector2 position;
private float rotation;
private float angularVelocity;
private float angularDamping;
private float angularSpring;
private Vector2 prevPosition;
private float prevZoom;
public float Shake;
private Vector2 shakePosition;
private float shakeTimer;
//the area of the world inside the camera view
private Rectangle worldView;
private float globalZoomScale = 1.0f;
private Point resolution;
private Vector2 targetPos;
//used to smooth out the movement when in freecam
private float targetZoom;
private Vector2 velocity;
public float Zoom
{
get { return zoom; }
set
{
zoom = MathHelper.Clamp(value, GameMain.DebugDraw ? 0.01f : MinZoom, MaxZoom);
Vector2 center = WorldViewCenter;
float newWidth = resolution.X / zoom;
float newHeight = resolution.Y / zoom;
worldView = new Rectangle(
(int)(center.X - newWidth / 2.0f),
(int)(center.Y + newHeight / 2.0f),
(int)newWidth,
(int)newHeight);
//UpdateTransform();
}
}
public float Rotation
{
get { return rotation; }
set
{
if (!MathUtils.IsValid(value)) return;
rotation = value;
}
}
public float AngularVelocity
{
get { return angularVelocity; }
set
{
if (!MathUtils.IsValid(value)) return;
angularVelocity = value;
}
}
public float OffsetAmount
{
get { return offsetAmount; }
set { offsetAmount = value; }
}
public Point Resolution
{
get { return resolution; }
}
public Rectangle WorldView
{
get { return worldView; }
}
public Vector2 WorldViewCenter
{
get
{
return new Vector2(
worldView.X + worldView.Width / 2.0f,
worldView.Y - worldView.Height / 2.0f);
}
}
public Matrix Transform
{
get { return transform; }
}
public Matrix ShaderTransform
{
get { return shaderTransform; }
}
public Camera()
{
zoom = prevZoom = targetZoom = 1.0f;
rotation = 0.0f;
position = Vector2.Zero;
CreateMatrices();
GameMain.Instance.OnResolutionChanged += () => { CreateMatrices(); };
UpdateTransform(false);
}
public Vector2 TargetPos
{
get { return targetPos; }
set { targetPos = value; }
}
public Vector2 GetPosition()
{
return position;
}
// Auxiliary function to move the camera
public void Translate(Vector2 amount)
{
position += amount;
}
public void ClientWrite(IWriteMessage msg)
{
if (Character.Controlled != null && !Character.Controlled.IsDead) { return; }
msg.Write((byte)ClientNetObject.SPECTATING_POS);
msg.Write(position.X);
msg.Write(position.Y);
}
private void CreateMatrices()
{
resolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
worldView = new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight);
viewMatrix = Matrix.CreateTranslation(new Vector3(GameMain.GraphicsWidth / 2.0f, GameMain.GraphicsHeight / 2.0f, 0));
globalZoomScale = (float)Math.Pow(new Vector2(resolution.X, resolution.Y).Length() / new Vector2(1920, 1080).Length(), 2);
}
public void UpdateTransform(bool interpolate = true)
{
Vector2 interpolatedPosition = interpolate ? Timing.Interpolate(prevPosition, position) : position;
float interpolatedZoom = interpolate ? Timing.Interpolate(prevZoom, zoom) : zoom;
worldView.X = (int)(interpolatedPosition.X - worldView.Width / 2.0);
worldView.Y = (int)(interpolatedPosition.Y + worldView.Height / 2.0);
transform = Matrix.CreateTranslation(
new Vector3(-interpolatedPosition.X, interpolatedPosition.Y, 0)) *
Matrix.CreateScale(new Vector3(interpolatedZoom, interpolatedZoom, 1)) *
Matrix.CreateRotationZ(rotation) * viewMatrix;
shaderTransform = Matrix.CreateTranslation(
new Vector3(
-interpolatedPosition.X - resolution.X / interpolatedZoom / 2.0f,
-interpolatedPosition.Y - resolution.Y / interpolatedZoom / 2.0f, 0)) *
Matrix.CreateScale(new Vector3(interpolatedZoom, interpolatedZoom, 1)) *
viewMatrix * Matrix.CreateRotationZ(-rotation);
if (Character.Controlled == null)
{
GameMain.SoundManager.ListenerPosition = new Vector3(WorldViewCenter.X, WorldViewCenter.Y, -(100.0f / zoom));
}
else
{
GameMain.SoundManager.ListenerPosition = new Vector3(Character.Controlled.WorldPosition.X, Character.Controlled.WorldPosition.Y, -(100.0f / zoom));
}
if (!interpolate)
{
prevPosition = position;
prevZoom = zoom;
}
}
private Vector2 previousOffset;
/// <summary>
/// Resets to false each time the MoveCamera method is called.
/// </summary>
public bool Freeze { get; set; }
public void MoveCamera(float deltaTime, bool allowMove = true, bool allowZoom = true)
{
prevPosition = position;
prevZoom = zoom;
float moveSpeed = 20.0f / zoom;
Vector2 moveCam = Vector2.Zero;
if (targetPos == Vector2.Zero)
{
Vector2 moveInput = Vector2.Zero;
if (allowMove && GUI.KeyboardDispatcher.Subscriber == null)
{
if (PlayerInput.KeyDown(Keys.LeftShift)) moveSpeed *= 2.0f;
if (PlayerInput.KeyDown(Keys.LeftControl)) moveSpeed *= 0.5f;
if (GameMain.Config.KeyBind(InputType.Left).IsDown()) moveInput.X -= 1.0f;
if (GameMain.Config.KeyBind(InputType.Right).IsDown()) moveInput.X += 1.0f;
if (GameMain.Config.KeyBind(InputType.Down).IsDown()) moveInput.Y -= 1.0f;
if (GameMain.Config.KeyBind(InputType.Up).IsDown()) moveInput.Y += 1.0f;
}
velocity = Vector2.Lerp(velocity, moveInput, deltaTime * 10.0f);
moveCam = velocity * moveSpeed * deltaTime * 60.0f;
if (Screen.Selected == GameMain.GameScreen && FollowSub)
{
var closestSub = Submarine.FindClosest(WorldViewCenter);
if (closestSub != null)
{
moveCam += FarseerPhysics.ConvertUnits.ToDisplayUnits(closestSub.Velocity * deltaTime);
}
}
if (allowZoom && GUI.MouseOn == null)
{
Vector2 mouseInWorld = ScreenToWorld(PlayerInput.MousePosition);
Vector2 diffViewCenter;
diffViewCenter = ((mouseInWorld - Position) * Zoom);
targetZoom = MathHelper.Clamp(
targetZoom + (PlayerInput.ScrollWheelSpeed / 1000.0f) * zoom,
GameMain.DebugDraw ? MinZoom * 0.1f : MinZoom,
MaxZoom);
Zoom = MathHelper.Lerp(Zoom, targetZoom, deltaTime * 10.0f);
if (!PlayerInput.KeyDown(Keys.F)) Position = mouseInWorld - (diffViewCenter / Zoom);
}
}
else if (allowMove)
{
Vector2 mousePos = PlayerInput.MousePosition;
Vector2 offset = mousePos - resolution.ToVector2() / 2;
offset.X = offset.X / (resolution.X * 0.4f);
offset.Y = -offset.Y / (resolution.Y * 0.3f);
if (offset.LengthSquared() > 1.0f) offset.Normalize();
offset *= offsetAmount;
// Freeze the camera movement by default, when the cursor is on top of an ui element.
// Setting a positive value to the OffsetAmount, will override this behaviour.
if (GUI.MouseOn != null && offsetAmount > 0)
{
Freeze = true;
}
if (CharacterHealth.OpenHealthWindow != null || CrewManager.IsCommandInterfaceOpen)
{
offset *= 0;
Freeze = false;
}
if (Freeze)
{
offset = previousOffset;
}
else
{
previousOffset = offset;
}
//how much to zoom out (zoom completely out when offset is 1000)
float zoomOutAmount = Math.Min(offset.Length() / 1000.0f, 1.0f);
//zoom amount when resolution is not taken into account
float unscaledZoom = MathHelper.Lerp(DefaultZoom, MinZoom, zoomOutAmount);
//zoom with resolution taken into account (zoom further out on smaller resolutions)
float scaledZoom = unscaledZoom * globalZoomScale;
//an ad-hoc way of allowing the players to have roughly the same maximum view distance regardless of the resolution,
//while still keeping the zoom around 1.0 when not looking further away (because otherwise we'd always be downsampling
//on lower resolutions, which doesn't look that good)
float newZoom = MathHelper.Lerp(unscaledZoom, scaledZoom,
(GameMain.Config == null || GameMain.Config.EnableMouseLook) ? (float)Math.Sqrt(zoomOutAmount) : 0.3f);
Zoom += (newZoom - zoom) / ZoomSmoothness;
//force targetzoom to the current zoom value, so the camera stays at the same zoom when switching to freecam
targetZoom = Zoom;
Vector2 diff = (targetPos + offset) - position;
moveCam = diff / MoveSmoothness;
}
rotation += angularVelocity * deltaTime;
angularVelocity *= (1.0f - angularDamping);
angularVelocity += -rotation * angularSpring;
angularDamping = 0.05f;
angularSpring = 0.2f;
if (Shake < 0.01f)
{
shakePosition = Vector2.Zero;
shakeTimer = 0.0f;
}
else
{
shakeTimer += deltaTime * 5.0f;
Vector2 noisePos = new Vector2((float)PerlinNoise.CalculatePerlin(shakeTimer, shakeTimer, 0) - 0.5f, (float)PerlinNoise.CalculatePerlin(shakeTimer, shakeTimer, 0.5f) - 0.5f);
shakePosition = noisePos * Shake * 2.0f;
Shake = MathHelper.Lerp(Shake, 0.0f, deltaTime * 2.0f);
}
Translate(moveCam + shakePosition);
Freeze = false;
}
public void StopMovement()
{
targetZoom = zoom;
velocity = Vector2.Zero;
angularVelocity = 0.0f;
rotation = 0.0f;
}
public Vector2 Position
{
get { return position; }
set
{
if (!MathUtils.IsValid(value))
{
return;
}
position = value;
}
}
public Vector2 ScreenToWorld(Vector2 coords)
{
Vector2 worldCoords = Vector2.Transform(coords, Matrix.Invert(transform));
return new Vector2(worldCoords.X, -worldCoords.Y);
}
public Vector2 WorldToScreen(Vector2 coords)
{
coords.Y = -coords.Y;
//Vector2 screenCoords = Vector2.Transform(coords, transform);
return Vector2.Transform(coords, transform);
}
}
}

View File

@@ -0,0 +1,56 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace Barotrauma
{
partial class AITarget
{
public static bool ShowAITargets;
public void Draw(SpriteBatch spriteBatch)
{
if (!ShowAITargets) { return; }
var pos = new Vector2(WorldPosition.X, -WorldPosition.Y);
if (soundRange > 0.0f)
{
Color color;
if (Entity is Character)
{
color = Color.Yellow;
}
else if (Entity is Item)
{
color = Color.Orange;
}
else
{
color = Color.OrangeRed;
}
ShapeExtensions.DrawCircle(spriteBatch, pos, SoundRange, 100, color, thickness: 1 / Screen.Selected.Cam.Zoom);
ShapeExtensions.DrawCircle(spriteBatch, pos, 3, 8, color, thickness: 2 / Screen.Selected.Cam.Zoom);
GUI.DrawLine(spriteBatch, pos, pos + Vector2.UnitY * SoundRange, color, width: (int)(1 / Screen.Selected.Cam.Zoom) + 1);
}
if (sightRange > 0.0f)
{
Color color;
if (Entity is Character)
{
color = Color.CornflowerBlue;
}
else if (Entity is Item)
{
color = Color.CadetBlue;
}
else
{
//color = Color.WhiteSmoke;
// disable the indicators for structures, because they clutter the debug view
return;
}
ShapeExtensions.DrawCircle(spriteBatch, pos, SightRange, 100, color, thickness: 1 / Screen.Selected.Cam.Zoom);
ShapeExtensions.DrawCircle(spriteBatch, pos, 6, 8, color, thickness: 2 / Screen.Selected.Cam.Zoom);
GUI.DrawLine(spriteBatch, pos, pos + Vector2.UnitY * SightRange, color, width: (int)(1 / Screen.Selected.Cam.Zoom) + 1);
}
}
}
}

View File

@@ -0,0 +1,132 @@
using FarseerPhysics;
using FarseerPhysics.Dynamics.Joints;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace Barotrauma
{
partial class EnemyAIController : AIController
{
public override void DebugDraw(SpriteBatch spriteBatch)
{
if (Character.IsDead) return;
Vector2 pos = Character.WorldPosition;
pos.Y = -pos.Y;
if (State == AIState.Idle && PreviousState == AIState.Attack)
{
var target = _selectedAiTarget ?? _lastAiTarget;
if (target != null && target.Entity != null)
{
var memory = GetTargetMemory(target);
Vector2 targetPos = memory.Location;
targetPos.Y = -targetPos.Y;
GUI.DrawLine(spriteBatch, pos, targetPos, Color.White * 0.5f, 0, 4);
GUI.DrawString(spriteBatch, pos - Vector2.UnitY * 60.0f, $"{target.Entity.ToString()} ({memory.Priority.FormatZeroDecimal()})", Color.White, Color.Black);
}
}
else if (SelectedAiTarget?.Entity != null)
{
Vector2 targetPos = SelectedAiTarget.WorldPosition;
if (State == AIState.Attack)
{
targetPos = attackWorldPos;
}
targetPos.Y = -targetPos.Y;
GUI.DrawLine(spriteBatch, pos, targetPos, GUI.Style.Red * 0.5f, 0, 4);
if (wallTarget != null)
{
Vector2 wallTargetPos = wallTarget.Position;
if (wallTarget.Structure.Submarine != null) { wallTargetPos += wallTarget.Structure.Submarine.Position; }
wallTargetPos.Y = -wallTargetPos.Y;
GUI.DrawRectangle(spriteBatch, wallTargetPos - new Vector2(10.0f, 10.0f), new Vector2(20.0f, 20.0f), Color.Orange, false);
GUI.DrawLine(spriteBatch, pos, wallTargetPos, Color.Orange * 0.5f, 0, 5);
}
GUI.DrawString(spriteBatch, pos - Vector2.UnitY * 60.0f, $"{SelectedAiTarget.Entity.ToString()} ({GetTargetMemory(SelectedAiTarget).Priority.FormatZeroDecimal()})", GUI.Style.Red, Color.Black);
GUI.DrawString(spriteBatch, pos - Vector2.UnitY * 40.0f, $"({targetValue.FormatZeroDecimal()})", GUI.Style.Red, Color.Black);
}
/*GUI.Font.DrawString(spriteBatch, targetValue.ToString(), pos - Vector2.UnitY * 80.0f, GUI.Style.Red);
GUI.Font.DrawString(spriteBatch, "updatetargets: " + MathUtils.Round(updateTargetsTimer, 0.1f), pos - Vector2.UnitY * 100.0f, GUI.Style.Red);
GUI.Font.DrawString(spriteBatch, "cooldown: " + MathUtils.Round(coolDownTimer, 0.1f), pos - Vector2.UnitY * 120.0f, GUI.Style.Red);*/
Color stateColor = Color.White;
switch (State)
{
case AIState.Attack:
stateColor = IsCoolDownRunning ? Color.Orange : GUI.Style.Red;
break;
case AIState.Escape:
stateColor = Color.LightBlue;
break;
case AIState.Flee:
stateColor = Color.White;
break;
case AIState.Eat:
stateColor = Color.Brown;
break;
}
GUI.DrawString(spriteBatch, pos - Vector2.UnitY * 80.0f, State.ToString(), stateColor, Color.Black);
if (LatchOntoAI != null)
{
foreach (Joint attachJoint in LatchOntoAI.AttachJoints)
{
GUI.DrawLine(spriteBatch,
ConvertUnits.ToDisplayUnits(new Vector2(attachJoint.WorldAnchorA.X, -attachJoint.WorldAnchorA.Y)),
ConvertUnits.ToDisplayUnits(new Vector2(attachJoint.WorldAnchorB.X, -attachJoint.WorldAnchorB.Y)), GUI.Style.Green, 0, 4);
}
if (LatchOntoAI.WallAttachPos.HasValue)
{
//GUI.DrawLine(spriteBatch, pos,
// ConvertUnits.ToDisplayUnits(new Vector2(LatchOntoAI.WallAttachPos.Value.X, -LatchOntoAI.WallAttachPos.Value.Y)), GUI.Style.Green, 0, 3);
}
}
if (steeringManager is IndoorsSteeringManager pathSteering)
{
var path = pathSteering.CurrentPath;
if (path != null)
{
if (path.CurrentNode != null)
{
GUI.DrawLine(spriteBatch, pos,
new Vector2(path.CurrentNode.DrawPosition.X, -path.CurrentNode.DrawPosition.Y),
Color.DarkViolet, 0, 3);
GUI.DrawString(spriteBatch, pos - new Vector2(0, 100), "Path cost: " + path.Cost.FormatZeroDecimal(), Color.White, Color.Black * 0.5f);
}
for (int i = 1; i < path.Nodes.Count; i++)
{
var previousNode = path.Nodes[i - 1];
var currentNode = path.Nodes[i];
GUI.DrawLine(spriteBatch,
new Vector2(currentNode.DrawPosition.X, -currentNode.DrawPosition.Y),
new Vector2(previousNode.DrawPosition.X, -previousNode.DrawPosition.Y),
GUI.Style.Red * 0.5f, 0, 3);
GUI.SmallFont.DrawString(spriteBatch,
currentNode.ID.ToString(),
new Vector2(currentNode.DrawPosition.X - 10, -currentNode.DrawPosition.Y - 30),
GUI.Style.Red);
}
}
}
else
{
if (steeringManager.AvoidDir.LengthSquared() > 0.0001f)
{
Vector2 hitPos = ConvertUnits.ToDisplayUnits(steeringManager.AvoidRayCastHitPosition);
hitPos.Y = -hitPos.Y;
GUI.DrawLine(spriteBatch, hitPos, hitPos + new Vector2(steeringManager.AvoidDir.X, -steeringManager.AvoidDir.Y) * 100, GUI.Style.Red, width: 5);
//GUI.DrawLine(spriteBatch, pos, ConvertUnits.ToDisplayUnits(steeringManager.AvoidLookAheadPos.X, -steeringManager.AvoidLookAheadPos.Y), Color.Orange, width: 4);
}
}
GUI.DrawLine(spriteBatch, pos, pos + ConvertUnits.ToDisplayUnits(new Vector2(Character.AnimController.TargetMovement.X, -Character.AnimController.TargetMovement.Y)), Color.SteelBlue, width: 2);
GUI.DrawLine(spriteBatch, pos, pos + ConvertUnits.ToDisplayUnits(new Vector2(Steering.X, -Steering.Y)), Color.Blue, width: 3);
}
}
}

View File

@@ -0,0 +1,102 @@
using Microsoft.Xna.Framework;
using FarseerPhysics;
using System.Linq;
namespace Barotrauma
{
partial class HumanAIController : AIController
{
partial void InitProjSpecific()
{
/*if (GameMain.GameSession != null && GameMain.GameSession.CrewManager != null)
{
CurrentOrder = Order.GetPrefab("dismissed");
objectiveManager.SetOrder(CurrentOrder, "", null);
GameMain.GameSession.CrewManager.SetCharacterOrder(Character, CurrentOrder, null, null);
}*/
}
public override void DebugDraw(Microsoft.Xna.Framework.Graphics.SpriteBatch spriteBatch)
{
Vector2 pos = Character.WorldPosition;
pos.Y = -pos.Y;
Vector2 textOffset = new Vector2(-40, -160);
if (SelectedAiTarget?.Entity != null)
{
//GUI.DrawLine(spriteBatch, pos, new Vector2(SelectedAiTarget.WorldPosition.X, -SelectedAiTarget.WorldPosition.Y), GUI.Style.Red);
//GUI.DrawString(spriteBatch, pos + textOffset, $"AI TARGET: {SelectedAiTarget.Entity.ToString()}", Color.White, Color.Black);
}
GUI.DrawString(spriteBatch, pos + textOffset, Character.Name, Color.White, Color.Black);
if (ObjectiveManager != null)
{
var currentOrder = ObjectiveManager.CurrentOrder;
if (currentOrder != null)
{
GUI.DrawString(spriteBatch, pos + textOffset + new Vector2(0, 20), $"ORDER: {currentOrder.DebugTag} ({currentOrder.Priority.FormatZeroDecimal()})", Color.White, Color.Black);
}
else if (ObjectiveManager.WaitTimer > 0)
{
GUI.DrawString(spriteBatch, pos + new Vector2(0, 20), $"Waiting... {ObjectiveManager.WaitTimer.FormatZeroDecimal()}", Color.White, Color.Black);
}
var currentObjective = ObjectiveManager.CurrentObjective;
if (currentObjective != null)
{
if (currentOrder == null)
{
GUI.DrawString(spriteBatch, pos + textOffset + new Vector2(0, 20), $"MAIN OBJECTIVE: {currentObjective.DebugTag} ({currentObjective.Priority.FormatZeroDecimal()})", Color.White, Color.Black);
}
var subObjective = currentObjective.CurrentSubObjective;
if (subObjective != null)
{
GUI.DrawString(spriteBatch, pos + textOffset + new Vector2(0, 40), $"SUBOBJECTIVE: {subObjective.DebugTag} ({subObjective.Priority.FormatZeroDecimal()})", Color.White, Color.Black);
}
var activeObjective = ObjectiveManager.GetActiveObjective();
if (activeObjective != null)
{
GUI.DrawString(spriteBatch, pos + textOffset + new Vector2(0, 60), $"ACTIVE OBJECTIVE: {activeObjective.DebugTag} ({activeObjective.Priority.FormatZeroDecimal()})", Color.White, Color.Black);
}
}
}
if (steeringManager is IndoorsSteeringManager pathSteering)
{
var path = pathSteering.CurrentPath;
if (path != null)
{
for (int i = 1; i < path.Nodes.Count; i++)
{
var previousNode = path.Nodes[i - 1];
var currentNode = path.Nodes[i];
GUI.DrawLine(spriteBatch,
new Vector2(currentNode.DrawPosition.X, -currentNode.DrawPosition.Y),
new Vector2(previousNode.DrawPosition.X, -previousNode.DrawPosition.Y),
Color.Blue * 0.5f, 0, 3);
GUI.SmallFont.DrawString(spriteBatch,
currentNode.ID.ToString(),
new Vector2(currentNode.DrawPosition.X - 10, -currentNode.DrawPosition.Y - 30),
Color.Blue);
}
if (path.CurrentNode != null)
{
GUI.DrawLine(spriteBatch, pos,
new Vector2(path.CurrentNode.DrawPosition.X, -path.CurrentNode.DrawPosition.Y),
Color.BlueViolet, 0, 3);
GUI.DrawString(spriteBatch, pos + textOffset + new Vector2(0, 80), "Path cost: " + path.Cost.FormatZeroDecimal(), Color.White, Color.Black * 0.5f);
}
}
}
GUI.DrawLine(spriteBatch, pos, pos + ConvertUnits.ToDisplayUnits(new Vector2(Character.AnimController.TargetMovement.X, -Character.AnimController.TargetMovement.Y)), Color.SteelBlue, width: 2);
GUI.DrawLine(spriteBatch, pos, pos + ConvertUnits.ToDisplayUnits(new Vector2(Steering.X, -Steering.Y)), Color.Blue, width: 3);
//if (Character.IsKeyDown(InputType.Aim))
//{
// GUI.DrawLine(spriteBatch, pos, new Vector2(Character.CursorWorldPosition.X, -Character.CursorWorldPosition.Y), Color.Yellow, width: 4);
//}
}
}
}

View File

@@ -0,0 +1,576 @@
using Barotrauma.Items.Components;
using Barotrauma.SpriteDeformations;
using Barotrauma.Extensions;
using FarseerPhysics;
using FarseerPhysics.Dynamics;
using FarseerPhysics.Dynamics.Joints;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Linq;
using System.Collections.Generic;
using Barotrauma.Particles;
namespace Barotrauma
{
abstract partial class Ragdoll
{
public HashSet<SpriteDeformation> SpriteDeformations { get; protected set; } = new HashSet<SpriteDeformation>();
/// <summary>
/// Inversed draw order, which is used for drawing the limbs in 3d (deformable sprites).
/// </summary>
protected Limb[] inversedLimbDrawOrder;
partial void UpdateNetPlayerPositionProjSpecific(float deltaTime, float lowestSubPos)
{
if (character != GameMain.Client.Character || !character.CanMove)
{
//remove states without a timestamp (there may still be ID-based states
//in the list when the controlled character switches to timestamp-based interpolation)
character.MemState.RemoveAll(m => m.Timestamp == 0.0f);
//use simple interpolation for other players' characters and characters that can't move
if (character.MemState.Count > 0)
{
CharacterStateInfo serverPos = character.MemState.Last();
if (!character.isSynced)
{
SetPosition(serverPos.Position, false);
Collider.LinearVelocity = Vector2.Zero;
character.MemLocalState.Clear();
character.LastNetworkUpdateID = serverPos.ID;
character.isSynced = true;
return;
}
if (character.MemState[0].SelectedCharacter == null || character.MemState[0].SelectedCharacter.Removed)
{
character.DeselectCharacter();
}
else if (character.MemState[0].SelectedCharacter != null)
{
character.SelectCharacter(character.MemState[0].SelectedCharacter);
}
if (character.MemState[0].SelectedItem == null || character.MemState[0].SelectedItem.Removed)
{
character.SelectedConstruction = null;
}
else
{
if (character.SelectedConstruction != character.MemState[0].SelectedItem)
{
foreach (var ic in character.MemState[0].SelectedItem.Components)
{
if (ic.CanBeSelected) ic.Select(character);
}
}
character.SelectedConstruction = character.MemState[0].SelectedItem;
}
if (character.MemState[0].Animation == AnimController.Animation.CPR)
{
character.AnimController.Anim = AnimController.Animation.CPR;
}
else if (character.AnimController.Anim == AnimController.Animation.CPR)
{
character.AnimController.Anim = AnimController.Animation.None;
}
Vector2 newVelocity = Collider.LinearVelocity;
Vector2 newPosition = Collider.SimPosition;
float newRotation = Collider.Rotation;
float newAngularVelocity = Collider.AngularVelocity;
Collider.CorrectPosition(character.MemState, out newPosition, out newVelocity, out newRotation, out newAngularVelocity);
newVelocity = newVelocity.ClampLength(100.0f);
if (!MathUtils.IsValid(newVelocity)) { newVelocity = Vector2.Zero; }
overrideTargetMovement = newVelocity.LengthSquared() > 0.01f ? newVelocity : Vector2.Zero;
Collider.LinearVelocity = newVelocity;
Collider.AngularVelocity = newAngularVelocity;
float distSqrd = Vector2.DistanceSquared(newPosition, Collider.SimPosition);
float errorTolerance = character.CanMove ? 0.01f : 0.2f;
if (distSqrd > errorTolerance)
{
if (distSqrd > 10.0f || !character.CanMove)
{
Collider.TargetRotation = newRotation;
SetPosition(newPosition, lerp: distSqrd < 5.0f, ignorePlatforms: false);
}
else
{
Collider.TargetRotation = newRotation;
Collider.TargetPosition = newPosition;
Collider.MoveToTargetPosition(true);
}
}
//immobilized characters can't correct their position using AnimController movement
// -> we need to correct it manually
if (!character.CanMove)
{
float mainLimbDistSqrd = Vector2.DistanceSquared(MainLimb.PullJointWorldAnchorA, Collider.SimPosition);
float mainLimbErrorTolerance = 0.1f;
//if the main limb is roughly at the correct position and the collider isn't moving (much at least),
//don't attempt to correct the position.
if (mainLimbDistSqrd > mainLimbErrorTolerance || Collider.LinearVelocity.LengthSquared() > 0.05f)
{
MainLimb.PullJointWorldAnchorB = Collider.SimPosition;
MainLimb.PullJointEnabled = true;
}
}
}
character.MemLocalState.Clear();
}
else
{
//remove states with a timestamp (there may still timestamp-based states
//in the list if the controlled character switches from timestamp-based interpolation to ID-based)
character.MemState.RemoveAll(m => m.Timestamp > 0.0f);
for (int i = 0; i < character.MemLocalState.Count; i++)
{
if (character.Submarine == null)
{
//transform in-sub coordinates to outside coordinates
if (character.MemLocalState[i].Position.Y > lowestSubPos)
{
character.MemLocalState[i].TransformInToOutside();
}
}
else if (currentHull?.Submarine != null)
{
//transform outside coordinates to in-sub coordinates
if (character.MemLocalState[i].Position.Y < lowestSubPos)
{
character.MemLocalState[i].TransformOutToInside(currentHull.Submarine);
}
}
}
if (character.MemState.Count < 1) return;
overrideTargetMovement = Vector2.Zero;
CharacterStateInfo serverPos = character.MemState.Last();
if (!character.isSynced)
{
SetPosition(serverPos.Position, false);
Collider.LinearVelocity = Vector2.Zero;
character.MemLocalState.Clear();
character.LastNetworkUpdateID = serverPos.ID;
character.isSynced = true;
return;
}
int localPosIndex = character.MemLocalState.FindIndex(m => m.ID == serverPos.ID);
if (localPosIndex > -1)
{
CharacterStateInfo localPos = character.MemLocalState[localPosIndex];
//the entity we're interacting with doesn't match the server's
if (localPos.SelectedCharacter != serverPos.SelectedCharacter)
{
if (serverPos.SelectedCharacter == null || serverPos.SelectedCharacter.Removed)
{
character.DeselectCharacter();
}
else if (serverPos.SelectedCharacter != null)
{
character.SelectCharacter(serverPos.SelectedCharacter);
}
}
if (localPos.SelectedItem != serverPos.SelectedItem)
{
if (serverPos.SelectedItem == null || serverPos.SelectedItem.Removed)
{
character.SelectedConstruction = null;
}
else if (serverPos.SelectedItem != null)
{
if (character.SelectedConstruction != serverPos.SelectedItem)
{
serverPos.SelectedItem.TryInteract(character, true, true);
}
character.SelectedConstruction = serverPos.SelectedItem;
}
}
if (localPos.Animation != serverPos.Animation)
{
if (serverPos.Animation == AnimController.Animation.CPR)
{
character.AnimController.Anim = AnimController.Animation.CPR;
}
else if (character.AnimController.Anim == AnimController.Animation.CPR)
{
character.AnimController.Anim = AnimController.Animation.None;
}
}
Hull serverHull = Hull.FindHull(ConvertUnits.ToDisplayUnits(serverPos.Position), character.CurrentHull, serverPos.Position.Y < lowestSubPos);
Hull clientHull = Hull.FindHull(ConvertUnits.ToDisplayUnits(localPos.Position), serverHull, localPos.Position.Y < lowestSubPos);
if (serverHull != null && clientHull != null && serverHull.Submarine != clientHull.Submarine)
{
//hull subs don't match => teleport the camera to the other sub
character.Submarine = serverHull.Submarine;
character.CurrentHull = CurrentHull = serverHull;
SetPosition(serverPos.Position);
character.MemLocalState.Clear();
}
else
{
Vector2 positionError = serverPos.Position - localPos.Position;
float rotationError = serverPos.Rotation.HasValue && localPos.Rotation.HasValue ?
serverPos.Rotation.Value - localPos.Rotation.Value :
0.0f;
for (int i = localPosIndex; i < character.MemLocalState.Count; i++)
{
Hull pointHull = Hull.FindHull(ConvertUnits.ToDisplayUnits(character.MemLocalState[i].Position), clientHull, character.MemLocalState[i].Position.Y < lowestSubPos);
if (pointHull != clientHull && ((pointHull == null) || (clientHull == null) || (pointHull.Submarine == clientHull.Submarine))) break;
character.MemLocalState[i].Translate(positionError, rotationError);
}
float errorMagnitude = positionError.Length();
if (errorMagnitude > 0.5f)
{
character.MemLocalState.Clear();
SetPosition(serverPos.Position, lerp: true, ignorePlatforms: false);
}
else if (errorMagnitude > 0.01f)
{
Collider.TargetPosition = Collider.SimPosition + positionError;
Collider.TargetRotation = Collider.Rotation + rotationError;
Collider.MoveToTargetPosition(lerp: true);
}
}
}
if (character.MemLocalState.Count > 120) character.MemLocalState.RemoveRange(0, character.MemLocalState.Count - 120);
character.MemState.Clear();
}
}
partial void ImpactProjSpecific(float impact, Body body)
{
float volume = MathHelper.Clamp(impact - 3.0f, 0.5f, 1.0f);
if (body.UserData is Limb limb && character.Stun <= 0f)
{
if (impact > 3.0f) { PlayImpactSound(limb); }
}
else if (body.UserData is Limb || body == Collider.FarseerBody)
{
if (!character.IsRemotePlayer && impact > ImpactTolerance)
{
SoundPlayer.PlayDamageSound("LimbBlunt", strongestImpact, Collider);
}
}
if (Character.Controlled == character)
{
GameMain.GameScreen.Cam.Shake = Math.Min(Math.Max(strongestImpact, GameMain.GameScreen.Cam.Shake), 3.0f);
}
}
public void PlayImpactSound(Limb limb)
{
limb.LastImpactSoundTime = (float)Timing.TotalTime;
if (!string.IsNullOrWhiteSpace(limb.HitSoundTag))
{
bool inWater = limb.inWater;
if (character.CurrentHull != null &&
character.CurrentHull.Surface > character.CurrentHull.Rect.Y - character.CurrentHull.Rect.Height &&
limb.SimPosition.Y < ConvertUnits.ToSimUnits(character.CurrentHull.Rect.Y - character.CurrentHull.Rect.Height) + limb.body.GetMaxExtent())
{
inWater = true;
}
SoundPlayer.PlaySound(inWater ? "footstep_water" : limb.HitSoundTag, limb.WorldPosition, hullGuess: character.CurrentHull);
}
foreach (WearableSprite wearable in limb.WearingItems)
{
if (limb.type == wearable.Limb && !string.IsNullOrWhiteSpace(wearable.Sound))
{
SoundPlayer.PlaySound(wearable.Sound, limb.WorldPosition, hullGuess: character.CurrentHull);
}
}
}
partial void Splash(Limb limb, Hull limbHull)
{
//create a splash particle
for (int i = 0; i < MathHelper.Clamp(Math.Abs(limb.LinearVelocity.Y), 1.0f, 5.0f); i++)
{
var splash = GameMain.ParticleManager.CreateParticle("watersplash",
new Vector2(limb.WorldPosition.X, limbHull.WorldSurface),
new Vector2(0.0f, Math.Abs(-limb.LinearVelocity.Y * 20.0f)) + Rand.Vector(Math.Abs(limb.LinearVelocity.Y * 10)),
Rand.Range(0.0f, MathHelper.TwoPi), limbHull);
if (splash != null)
{
splash.Size *= MathHelper.Clamp(Math.Abs(limb.LinearVelocity.Y) * 0.1f, 1.0f, 2.0f);
}
}
GameMain.ParticleManager.CreateParticle("bubbles",
new Vector2(limb.WorldPosition.X, limbHull.WorldSurface),
limb.LinearVelocity * 0.001f,
0.0f, limbHull);
//if the Character dropped into water, create a wave
if (limb.LinearVelocity.Y < 0.0f)
{
if (splashSoundTimer <= 0.0f)
{
SoundPlayer.PlaySplashSound(limb.WorldPosition, Math.Abs(limb.LinearVelocity.Y) + Rand.Range(-5.0f, 0.0f));
splashSoundTimer = 0.5f;
}
//+ some extra bubbles to follow the character underwater
GameMain.ParticleManager.CreateParticle("bubbles",
new Vector2(limb.WorldPosition.X, limbHull.WorldSurface),
limb.LinearVelocity * 10.0f,
0.0f, limbHull);
}
}
partial void SetupDrawOrder()
{
//make sure every character gets drawn at a distinct "layer"
//(instead of having some of the limbs appear behind and some in front of other characters)
float startDepth = 0.1f;
float increment = 0.001f;
foreach (Character otherCharacter in Character.CharacterList)
{
if (otherCharacter == character) continue;
startDepth += increment;
}
//make sure each limb has a distinct depth value
List<Limb> depthSortedLimbs = Limbs.OrderBy(l => l.ActiveSprite == null ? 0.0f : l.ActiveSprite.Depth).ToList();
foreach (Limb limb in Limbs)
{
if (limb.ActiveSprite != null)
limb.ActiveSprite.Depth = startDepth + depthSortedLimbs.IndexOf(limb) * 0.00001f;
}
depthSortedLimbs.Reverse();
inversedLimbDrawOrder = depthSortedLimbs.ToArray();
}
partial void UpdateProjSpecific(float deltaTime, Camera cam)
{
if (!character.IsVisible) { return; }
LimbJoints.ForEach(j => j.UpdateDeformations(deltaTime));
foreach (var deformation in SpriteDeformations)
{
if (character.IsDead && deformation.Params.StopWhenHostIsDead) { continue; }
if (deformation.Params.UseMovementSine)
{
if (this is AnimController animator)
{
deformation.Phase = MathUtils.WrapAngleTwoPi(animator.WalkPos * deformation.Params.Frequency + MathHelper.Pi * deformation.Params.SineOffset);
}
}
else
{
deformation.Update(deltaTime);
}
}
}
partial void FlipProjSpecific()
{
foreach (Limb limb in Limbs)
{
if (limb == null || limb.IsSevered || limb.ActiveSprite == null) continue;
Vector2 spriteOrigin = limb.ActiveSprite.Origin;
spriteOrigin.X = limb.ActiveSprite.SourceRect.Width - spriteOrigin.X;
limb.ActiveSprite.Origin = spriteOrigin;
}
}
partial void SeverLimbJointProjSpecific(LimbJoint limbJoint, bool playSound)
{
foreach (Limb limb in new Limb[] { limbJoint.LimbA, limbJoint.LimbB })
{
float gibParticleAmount = MathHelper.Clamp(limb.Mass / character.AnimController.Mass, 0.1f, 1.0f);
foreach (ParticleEmitter emitter in character.GibEmitters)
{
if (inWater && emitter.Prefab.ParticlePrefab.DrawTarget == ParticlePrefab.DrawTargetType.Air) continue;
if (!inWater && emitter.Prefab.ParticlePrefab.DrawTarget == ParticlePrefab.DrawTargetType.Water) continue;
emitter.Emit(1.0f, limb.WorldPosition, character.CurrentHull, amountMultiplier: gibParticleAmount);
}
if (!string.IsNullOrEmpty(character.BloodDecalName))
{
character.CurrentHull?.AddDecal(character.BloodDecalName, limb.WorldPosition, MathHelper.Clamp(limb.Mass, 0.5f, 2.0f));
}
}
if (playSound)
{
SoundPlayer.PlayDamageSound("Gore", 1.0f, limbJoint.LimbA.body);
}
}
public void Draw(SpriteBatch spriteBatch, Camera cam)
{
if (simplePhysicsEnabled) { return; }
Collider.UpdateDrawPosition();
if (Limbs == null)
{
DebugConsole.ThrowError("Failed to draw a ragdoll, limbs have been removed. Character: \"" + character.Name + "\", removed: " + character.Removed + "\n" + Environment.StackTrace);
GameAnalyticsManager.AddErrorEventOnce("Ragdoll.Draw:LimbsRemoved",
GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
"Failed to draw a ragdoll, limbs have been removed. Character: \"" + character.Name + "\", removed: " + character.Removed + "\n" + Environment.StackTrace);
return;
}
Color? color = null;
if (character.ExternalHighlight)
{
color = Color.Lerp(Color.White, GUI.Style.Orange, (float)Math.Sin(Timing.TotalTime * 3.5f));
}
float depthOffset = GetDepthOffset();
for (int i = 0; i < limbs.Length; i++)
{
if (depthOffset != 0.0f) { inversedLimbDrawOrder[i].ActiveSprite.Depth += depthOffset; }
inversedLimbDrawOrder[i].Draw(spriteBatch, cam, color);
if (depthOffset != 0.0f) { inversedLimbDrawOrder[i].ActiveSprite.Depth -= depthOffset; }
}
LimbJoints.ForEach(j => j.Draw(spriteBatch));
}
/// <summary>
/// Offset added to the default draw depth of the character's limbs. For example, climbing on ladders affects the depth of the character to get it to render behind the ladders.
/// </summary>
public float GetDepthOffset()
{
float depthOffset = 0.0f;
var ladder = character.SelectedConstruction?.GetComponent<Ladder>();
if (ladder != null)
{
float maxDepth = 0.0f;
float minDepth = 1.0f;
foreach (Limb limb in Limbs)
{
var activeSprite = limb.ActiveSprite;
if (activeSprite != null)
{
maxDepth = Math.Max(activeSprite.Depth, maxDepth);
minDepth = Math.Min(activeSprite.Depth, minDepth);
}
}
if (character.WorldPosition.X < character.SelectedConstruction.WorldPosition.X)
{
//at the left side of the ladder, needs to be drawn in front of the rungs
depthOffset = Math.Max(ladder.BackgroundSpriteDepth - 0.01f - maxDepth, 0.0f);
}
else
{
//at the right side of the ladder, needs to be drawn behind the rungs
depthOffset = Math.Max(ladder.BackgroundSpriteDepth + 0.01f - minDepth, 0.0f);
}
}
return depthOffset;
}
public void DebugDraw(SpriteBatch spriteBatch)
{
if (!GameMain.DebugDraw || !character.Enabled) return;
if (simplePhysicsEnabled) return;
foreach (Limb limb in Limbs)
{
if (limb.PullJointEnabled)
{
Vector2 pos = ConvertUnits.ToDisplayUnits(limb.PullJointWorldAnchorA);
if (currentHull?.Submarine != null) pos += currentHull.Submarine.DrawPosition;
pos.Y = -pos.Y;
GUI.DrawRectangle(spriteBatch, new Rectangle((int)pos.X, (int)pos.Y, 5, 5), GUI.Style.Red, true, 0.01f);
}
limb.body.DebugDraw(spriteBatch, inWater ? (currentHull == null ? Color.Blue : Color.Cyan) : Color.White);
}
Collider.DebugDraw(spriteBatch, frozen ? GUI.Style.Red : (inWater ? Color.SkyBlue : Color.Gray));
GUI.Font.DrawString(spriteBatch, Collider.LinearVelocity.X.FormatSingleDecimal(), new Vector2(Collider.DrawPosition.X, -Collider.DrawPosition.Y), Color.Orange);
foreach (RevoluteJoint joint in LimbJoints)
{
Vector2 pos = ConvertUnits.ToDisplayUnits(joint.WorldAnchorA);
GUI.DrawRectangle(spriteBatch, new Rectangle((int)pos.X, (int)-pos.Y, 5, 5), Color.White, true);
pos = ConvertUnits.ToDisplayUnits(joint.WorldAnchorB);
GUI.DrawRectangle(spriteBatch, new Rectangle((int)pos.X, (int)-pos.Y, 5, 5), Color.White, true);
}
foreach (Limb limb in Limbs)
{
if (limb.body.TargetPosition != null)
{
Vector2 pos = ConvertUnits.ToDisplayUnits((Vector2)limb.body.TargetPosition);
if (currentHull?.Submarine != null) pos += currentHull.Submarine.DrawPosition;
pos.Y = -pos.Y;
GUI.DrawRectangle(spriteBatch, new Rectangle((int)pos.X - 10, (int)pos.Y - 10, 20, 20), Color.Cyan, false, 0.01f);
GUI.DrawLine(spriteBatch, pos, new Vector2(limb.WorldPosition.X, -limb.WorldPosition.Y), Color.Cyan);
}
}
if (this is HumanoidAnimController humanoid)
{
Vector2 pos = ConvertUnits.ToDisplayUnits(humanoid.RightHandIKPos);
GUI.DrawRectangle(spriteBatch, new Rectangle((int)pos.X, (int)-pos.Y, 4, 4), GUI.Style.Green, true);
pos = ConvertUnits.ToDisplayUnits(humanoid.LeftHandIKPos);
GUI.DrawRectangle(spriteBatch, new Rectangle((int)pos.X, (int)-pos.Y, 4, 4), GUI.Style.Green, true);
}
if (character.MemState.Count > 1)
{
Vector2 prevPos = ConvertUnits.ToDisplayUnits(character.MemState[0].Position);
if (currentHull?.Submarine != null) prevPos += currentHull.Submarine.DrawPosition;
prevPos.Y = -prevPos.Y;
for (int i = 1; i < character.MemState.Count; i++)
{
Vector2 currPos = ConvertUnits.ToDisplayUnits(character.MemState[i].Position);
if (currentHull?.Submarine != null) currPos += currentHull.Submarine.DrawPosition;
currPos.Y = -currPos.Y;
GUI.DrawRectangle(spriteBatch, new Rectangle((int)currPos.X - 3, (int)currPos.Y - 3, 6, 6), Color.Cyan * 0.6f, true, 0.01f);
GUI.DrawLine(spriteBatch, prevPos, currPos, Color.Cyan * 0.6f, 0, 3);
prevPos = currPos;
}
}
if (currentHull != null)
{
Vector2 displayFloorPos = ConvertUnits.ToDisplayUnits(new Vector2(Collider.SimPosition.X, floorY));
if (currentHull?.Submarine != null) { displayFloorPos += currentHull.Submarine.DrawPosition; }
displayFloorPos.Y = -displayFloorPos.Y;
GUI.DrawLine(spriteBatch, displayFloorPos, displayFloorPos + new Vector2(floorNormal.X, -floorNormal.Y) * 50.0f, Color.Cyan * 0.5f, 0, 2);
}
if (IgnorePlatforms)
{
GUI.DrawLine(spriteBatch,
new Vector2(Collider.DrawPosition.X, -Collider.DrawPosition.Y),
new Vector2(Collider.DrawPosition.X, -Collider.DrawPosition.Y + 50),
Color.Orange, 0, 5);
}
}
}
}

View File

@@ -0,0 +1,878 @@
using Barotrauma.Items.Components;
using Barotrauma.Networking;
using Barotrauma.Particles;
using Barotrauma.Sounds;
using FarseerPhysics;
using FarseerPhysics.Dynamics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma
{
partial class Character
{
public static bool DisableControls;
public static bool DebugDrawInteract;
protected float soundTimer;
protected float soundInterval;
protected float hudInfoTimer;
protected bool hudInfoVisible;
private float pressureParticleTimer;
private float findFocusedTimer;
protected float lastRecvPositionUpdateTime;
private float hudInfoHeight;
private List<CharacterSound> sounds;
public bool ExternalHighlight;
/// <summary>
/// Is the character currently visible on the camera. Refresh the value by calling DoVisibilityCheck.
/// </summary>
public bool IsVisible
{
get;
private set;
} = true;
//the Character that the player is currently controlling
private static Character controlled;
public static Character Controlled
{
get { return controlled; }
set
{
if (controlled == value) return;
controlled = value;
if (controlled != null) controlled.Enabled = true;
CharacterHealth.OpenHealthWindow = null;
}
}
private Dictionary<object, HUDProgressBar> hudProgressBars;
private readonly List<KeyValuePair<object, HUDProgressBar>> progressBarRemovals = new List<KeyValuePair<object, HUDProgressBar>>();
public Dictionary<object, HUDProgressBar> HUDProgressBars
{
get { return hudProgressBars; }
}
private float blurStrength;
public float BlurStrength
{
get { return blurStrength; }
set { blurStrength = MathHelper.Clamp(value, 0.0f, 1.0f); }
}
private float distortStrength;
public float DistortStrength
{
get { return distortStrength; }
set { distortStrength = MathHelper.Clamp(value, 0.0f, 1.0f); }
}
private float radialDistortStrength;
public float RadialDistortStrength
{
get { return radialDistortStrength; }
set { radialDistortStrength = MathHelper.Clamp(value, 0.0f, 100.0f); }
}
private float chromaticAberrationStrength;
public float ChromaticAberrationStrength
{
get { return chromaticAberrationStrength; }
set { chromaticAberrationStrength = MathHelper.Clamp(value, 0.0f, 100.0f); }
}
public string BloodDecalName => Params.BloodDecal;
private readonly List<ParticleEmitter> bloodEmitters = new List<ParticleEmitter>();
public IEnumerable<ParticleEmitter> BloodEmitters
{
get { return bloodEmitters; }
}
private readonly List<ParticleEmitter> damageEmitters = new List<ParticleEmitter>();
public IEnumerable<ParticleEmitter> DamageEmitters
{
get { return damageEmitters; }
}
private readonly List<ParticleEmitter> gibEmitters = new List<ParticleEmitter>();
public IEnumerable<ParticleEmitter> GibEmitters
{
get { return gibEmitters; }
}
public class ObjectiveEntity
{
public Entity Entity;
public Sprite Sprite;
public Color Color;
public ObjectiveEntity(Entity entity, Sprite sprite, Color? color = null)
{
Entity = entity;
Sprite = sprite;
if (color.HasValue)
{
Color = color.Value;
}
else
{
Color = Color.White;
}
}
}
private readonly List<ObjectiveEntity> activeObjectiveEntities = new List<ObjectiveEntity>();
public IEnumerable<ObjectiveEntity> ActiveObjectiveEntities
{
get { return activeObjectiveEntities; }
}
partial void InitProjSpecific(XElement mainElement)
{
soundInterval = mainElement.GetAttributeFloat("soundinterval", 10.0f);
soundTimer = Rand.Range(0.0f, soundInterval);
sounds = new List<CharacterSound>();
Params.Sounds.ForEach(s => sounds.Add(new CharacterSound(s)));
foreach (XElement subElement in mainElement.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "damageemitter":
damageEmitters.Add(new ParticleEmitter(subElement));
break;
case "bloodemitter":
bloodEmitters.Add(new ParticleEmitter(subElement));
break;
case "gibemitter":
gibEmitters.Add(new ParticleEmitter(subElement));
break;
}
}
hudProgressBars = new Dictionary<object, HUDProgressBar>();
}
partial void UpdateLimbLightSource(Limb limb)
{
if (limb.LightSource != null)
{
limb.LightSource.Enabled = enabled;
}
}
private bool wasFiring;
/// <summary>
/// Control the Character according to player input
/// </summary>
public void ControlLocalPlayer(float deltaTime, Camera cam, bool moveCam = true)
{
if (DisableControls || GUI.PauseMenuOpen || GUI.SettingsMenuOpen)
{
foreach (Key key in keys)
{
if (key == null) continue;
key.Reset();
}
}
else
{
wasFiring |= keys[(int)InputType.Aim].Held && keys[(int)InputType.Shoot].Held;
for (int i = 0; i < keys.Length; i++)
{
keys[i].SetState();
}
//if we were firing (= pressing the aim and shoot keys at the same time)
//and the fire key is the same as Select or Use, reset the key to prevent accidentally selecting/using items
if (wasFiring && !keys[(int)InputType.Shoot].Held)
{
if (GameMain.Config.KeyBind(InputType.Shoot).Equals(GameMain.Config.KeyBind(InputType.Select)))
{
keys[(int)InputType.Select].Reset();
}
if (GameMain.Config.KeyBind(InputType.Shoot).Equals(GameMain.Config.KeyBind(InputType.Use)))
{
keys[(int)InputType.Use].Reset();
}
wasFiring = false;
}
float targetOffsetAmount = 0.0f;
if (moveCam)
{
if (NeedsAir &&
pressureProtection < 80.0f &&
(AnimController.CurrentHull == null || AnimController.CurrentHull.LethalPressure > 0.0f))
{
float pressure = AnimController.CurrentHull == null ? 100.0f : AnimController.CurrentHull.LethalPressure;
if (pressure > 0.0f)
{
float zoomInEffectStrength = MathHelper.Clamp(pressure / 100.0f, 0.1f, 1.0f);
cam.Zoom = MathHelper.Lerp(cam.Zoom,
cam.DefaultZoom + (Math.Max(pressure, 10) / 150.0f) * Rand.Range(0.9f, 1.1f),
zoomInEffectStrength);
pressureParticleTimer += pressure * deltaTime;
if (pressureParticleTimer > 10.0f)
{
Particle p = GameMain.ParticleManager.CreateParticle("waterblood", WorldPosition + Rand.Vector(5.0f), Rand.Vector(10.0f));
pressureParticleTimer = 0.0f;
}
}
}
if (IsHumanoid)
{
cam.OffsetAmount = 250.0f;// MathHelper.Lerp(cam.OffsetAmount, 250.0f, deltaTime);
}
else
{
//increased visibility range when controlling large a non-humanoid
cam.OffsetAmount = MathHelper.Clamp(Mass, 250.0f, 1500.0f);
}
}
cursorPosition = cam.ScreenToWorld(PlayerInput.MousePosition);
if (AnimController.CurrentHull?.Submarine != null)
{
cursorPosition -= AnimController.CurrentHull.Submarine.Position;
}
Vector2 mouseSimPos = ConvertUnits.ToSimUnits(cursorPosition);
if (GUI.PauseMenuOpen)
{
cam.OffsetAmount = targetOffsetAmount = 0.0f;
}
else if (Lights.LightManager.ViewTarget is Item item && item.Prefab.FocusOnSelected)
{
cam.OffsetAmount = targetOffsetAmount = item.Prefab.OffsetOnSelected;
}
else if (SelectedConstruction != null && ViewTarget == null &&
SelectedConstruction.Components.Any(ic => ic?.GuiFrame != null && ic.ShouldDrawHUD(this)))
{
cam.OffsetAmount = targetOffsetAmount = 0.0f;
cursorPosition =
SelectedConstruction.Position +
new Vector2(cursorPosition.X % 10.0f, cursorPosition.Y % 10.0f); //apply a little bit of movement to the cursor pos to prevent AFK kicking
}
else if (!GameMain.Config.EnableMouseLook)
{
cam.OffsetAmount = targetOffsetAmount = 0.0f;
}
else if (Lights.LightManager.ViewTarget == this)
{
if (GUI.PauseMenuOpen || IsUnconscious)
{
if (deltaTime > 0.0f)
{
cam.OffsetAmount = targetOffsetAmount = 0.0f;
}
}
else if (Vector2.DistanceSquared(AnimController.Limbs[0].SimPosition, mouseSimPos) > 1.0f)
{
Body body = Submarine.CheckVisibility(AnimController.Limbs[0].SimPosition, mouseSimPos);
Structure structure = body?.UserData as Structure;
float sightDist = Submarine.LastPickedFraction;
if (body?.UserData is Structure && !((Structure)body.UserData).CastShadow)
{
sightDist = 1.0f;
}
targetOffsetAmount = Math.Max(250.0f, sightDist * 500.0f);
}
}
cam.OffsetAmount = MathHelper.Lerp(cam.OffsetAmount, targetOffsetAmount, 0.05f);
DoInteractionUpdate(deltaTime, mouseSimPos);
}
if (!GUI.PauseMenuOpen && !GUI.SettingsMenuOpen)
{
if (SelectedConstruction != null &&
(SelectedConstruction.ActiveHUDs.Any(ic => ic.GuiFrame != null && HUD.CloseHUD(ic.GuiFrame.Rect)) ||
((ViewTarget as Item)?.Prefab.FocusOnSelected ?? false) && PlayerInput.KeyHit(Microsoft.Xna.Framework.Input.Keys.Escape)))
{
if (GameMain.Client != null)
{
//emulate a Select input to get the character to deselect the item server-side
//keys[(int)InputType.Select].Hit = true;
keys[(int)InputType.Deselect].Hit = true;
}
//reset focus to prevent us from accidentally interacting with another entity
focusedItem = null;
FocusedCharacter = null;
findFocusedTimer = 0.2f;
SelectedConstruction = null;
}
}
DisableControls = false;
}
partial void UpdateControlled(float deltaTime, Camera cam)
{
if (controlled != this) return;
ControlLocalPlayer(deltaTime, cam);
Lights.LightManager.ViewTarget = this;
CharacterHUD.Update(deltaTime, this, cam);
if (hudProgressBars.Any())
{
foreach (var progressBar in hudProgressBars)
{
if (progressBar.Value.FadeTimer <= 0.0f)
{
progressBarRemovals.Add(progressBar);
continue;
}
progressBar.Value.Update(deltaTime);
}
if (progressBarRemovals.Any())
{
progressBarRemovals.ForEach(pb => hudProgressBars.Remove(pb.Key));
progressBarRemovals.Clear();
}
}
}
partial void OnAttackedProjSpecific(Character attacker, AttackResult attackResult)
{
if (attackResult.Damage <= 1.0f || IsDead) { return; }
if (soundTimer < soundInterval * 0.5f)
{
PlaySound(CharacterSound.SoundType.Damage);
soundTimer = soundInterval;
}
}
partial void KillProjSpecific(CauseOfDeathType causeOfDeath, Affliction causeOfDeathAffliction)
{
if (GameMain.NetworkMember != null && controlled == this)
{
string chatMessage = CauseOfDeath.Type == CauseOfDeathType.Affliction ?
CauseOfDeath.Affliction.SelfCauseOfDeathDescription :
TextManager.Get("Self_CauseOfDeathDescription." + CauseOfDeath.Type.ToString(), fallBackTag: "Self_CauseOfDeathDescription.Damage");
if (GameMain.Client != null) chatMessage += " " + TextManager.Get("DeathChatNotification");
GameMain.NetworkMember.AddChatMessage(chatMessage, ChatMessageType.Dead);
GameMain.LightManager.LosEnabled = false;
controlled = null;
}
PlaySound(CharacterSound.SoundType.Die);
}
partial void DisposeProjSpecific()
{
if (controlled == this) controlled = null;
if (GameMain.GameSession?.CrewManager != null &&
GameMain.GameSession.CrewManager.GetCharacters().Contains(this))
{
GameMain.GameSession.CrewManager.RemoveCharacter(this);
}
if (GameMain.Client?.Character == this) GameMain.Client.Character = null;
if (Lights.LightManager.ViewTarget == this) Lights.LightManager.ViewTarget = null;
}
private List<Item> debugInteractablesInRange = new List<Item>();
private List<Item> debugInteractablesAtCursor = new List<Item>();
private List<Pair<Item, float>> debugInteractablesNearCursor = new List<Pair<Item, float>>();
/// <summary>
/// Finds the front (lowest depth) interactable item at a position. "Interactable" in this case means that the character can "reach" the item.
/// </summary>
/// <param name="character">The Character who is looking for the interactable item, only items that are close enough to this character are returned</param>
/// <param name="simPosition">The item at the simPosition, with the lowest depth, is returned</param>
/// <param name="allowFindingNearestItem">If this is true and an item cannot be found at simPosition then a nearest item will be returned if possible</param>
/// <param name="hull">If a hull is specified, only items within that hull are returned</param>
public Item FindItemAtPosition(Vector2 simPosition, float aimAssistModifier = 0.0f, Item[] ignoredItems = null)
{
if (Submarine != null)
{
simPosition += Submarine.SimPosition;
}
debugInteractablesInRange.Clear();
debugInteractablesAtCursor.Clear();
debugInteractablesNearCursor.Clear();
bool draggingItemToWorld = CharacterInventory.DraggingItemToWorld;
//reduce the amount of aim assist if an item has been selected
//= can't switch selection to another item without deselecting the current one first UNLESS the cursor is directly on the item
//otherwise it would be too easy to accidentally switch the selected item when rewiring items
float aimAssistAmount = SelectedConstruction == null ? 100.0f * aimAssistModifier : 1.0f;
Vector2 displayPosition = ConvertUnits.ToDisplayUnits(simPosition);
//use the list of visible entities if it exists
var entityList = Submarine.VisibleEntities ?? Item.ItemList;
Item closestItem = null;
float closestItemDistance = Math.Max(aimAssistAmount, 2.0f);
foreach (MapEntity entity in entityList)
{
if (!(entity is Item item))
{
continue;
}
if (item.body != null && !item.body.Enabled) continue;
if (item.ParentInventory != null) continue;
if (ignoredItems != null && ignoredItems.Contains(item)) continue;
if (draggingItemToWorld)
{
if (item.OwnInventory == null ||
!item.OwnInventory.CanBePut(CharacterInventory.draggingItem) ||
!CanAccessInventory(item.OwnInventory))
{
continue;
}
}
float distanceToItem = float.PositiveInfinity;
if (item.IsInsideTrigger(displayPosition, out Rectangle transformedTrigger))
{
debugInteractablesAtCursor.Add(item);
//distance is between 0-1 when the cursor is directly on the item
distanceToItem =
Math.Abs(transformedTrigger.Center.X - displayPosition.X) / transformedTrigger.Width +
Math.Abs((transformedTrigger.Y - transformedTrigger.Height / 2.0f) - displayPosition.Y) / transformedTrigger.Height;
//modify the distance based on the size of the trigger (preferring smaller items)
distanceToItem *= MathHelper.Lerp(0.05f, 2.0f, (transformedTrigger.Width + transformedTrigger.Height) / 250.0f);
}
else
{
Rectangle itemDisplayRect = new Rectangle(item.InteractionRect.X, item.InteractionRect.Y - item.InteractionRect.Height, item.InteractionRect.Width, item.InteractionRect.Height);
if (itemDisplayRect.Contains(displayPosition))
{
debugInteractablesAtCursor.Add(item);
//distance is between 0-1 when the cursor is directly on the item
distanceToItem =
Math.Abs(itemDisplayRect.Center.X - displayPosition.X) / itemDisplayRect.Width +
Math.Abs(itemDisplayRect.Center.Y - displayPosition.Y) / itemDisplayRect.Height;
//modify the distance based on the size of the item (preferring smaller ones)
distanceToItem *= MathHelper.Lerp(0.05f, 2.0f, (itemDisplayRect.Width + itemDisplayRect.Height) / 250.0f);
}
else
{
if (closestItemDistance < 2.0f) { continue; }
//get the point on the itemDisplayRect which is closest to the cursor
Vector2 rectIntersectionPoint = new Vector2(
MathHelper.Clamp(displayPosition.X, itemDisplayRect.X, itemDisplayRect.Right),
MathHelper.Clamp(displayPosition.Y, itemDisplayRect.Y, itemDisplayRect.Bottom));
distanceToItem = 2.0f + Vector2.Distance(rectIntersectionPoint, displayPosition);
}
}
if (distanceToItem > closestItemDistance) { continue; }
if (!CanInteractWith(item)) { continue; }
debugInteractablesNearCursor.Add(new Pair<Item, float>(item, 1.0f - distanceToItem / (100.0f * aimAssistModifier)));
closestItem = item;
closestItemDistance = distanceToItem;
}
return closestItem;
}
private Character FindCharacterAtPosition(Vector2 mouseSimPos, float maxDist = 150.0f)
{
Character closestCharacter = null;
float closestDist = 0.0f;
maxDist = ConvertUnits.ToSimUnits(maxDist);
foreach (Character c in CharacterList)
{
if (!CanInteractWith(c, checkVisibility: false)) continue;
float dist = Vector2.DistanceSquared(mouseSimPos, c.SimPosition);
if (dist < maxDist * maxDist && (closestCharacter == null || dist < closestDist))
{
closestCharacter = c;
closestDist = dist;
}
/*FarseerPhysics.Common.Transform transform;
c.AnimController.Collider.FarseerBody.GetTransform(out transform);
for (int i = 0; i < c.AnimController.Collider.FarseerBody.FixtureList.Count; i++)
{
if (c.AnimController.Collider.FarseerBody.FixtureList[i].Shape.TestPoint(ref transform, ref mouseSimPos))
{
Console.WriteLine("Hit: " + i);
closestCharacter = c;
}
}*/
}
return closestCharacter;
}
public bool ShouldLockHud()
{
if (this != controlled) { return false; }
//lock if using a controller, except if we're also using a connection panel in the same item
return
SelectedConstruction != null &&
SelectedConstruction?.GetComponent<Controller>()?.User == this &&
SelectedConstruction?.GetComponent<ConnectionPanel>()?.User != this;
}
partial void UpdateProjSpecific(float deltaTime, Camera cam)
{
if (!enabled) { return; }
if (!IsDead && !IsUnconscious)
{
if (soundTimer > 0)
{
soundTimer -= deltaTime;
}
else if (AIController != null)
{
switch (AIController.State)
{
case AIState.Attack:
PlaySound(CharacterSound.SoundType.Attack);
break;
default:
PlaySound(CharacterSound.SoundType.Idle);
break;
}
}
}
if (info != null || Vitality < MaxVitality * 0.98f)
{
hudInfoTimer -= deltaTime;
if (hudInfoTimer <= 0.0f)
{
if (controlled == null)
{
hudInfoVisible = true;
}
//if the character is not in the camera view, the name can't be visible and we can avoid the expensive visibility checks
else if (WorldPosition.X < cam.WorldView.X || WorldPosition.X > cam.WorldView.Right ||
WorldPosition.Y > cam.WorldView.Y || WorldPosition.Y < cam.WorldView.Y - cam.WorldView.Height)
{
hudInfoVisible = false;
}
else
{
//Ideally it shouldn't send the character entirely if we can't see them but /shrug, this isn't the most hacker-proof game atm
hudInfoVisible = controlled.CanSeeCharacter(this, controlled.ViewTarget == null ? controlled.WorldPosition : controlled.ViewTarget.WorldPosition);
}
hudInfoTimer = Rand.Range(0.5f, 1.0f);
}
}
CharacterHealth.UpdateClientSpecific(deltaTime);
if (controlled == this)
{
CharacterHealth.UpdateHUD(deltaTime);
}
}
partial void SetOrderProjSpecific(Order order, string orderOption)
{
GameMain.GameSession?.CrewManager?.DisplayCharacterOrder(this, order, orderOption);
}
public static void AddAllToGUIUpdateList()
{
for (int i = 0; i < CharacterList.Count; i++)
{
CharacterList[i].AddToGUIUpdateList();
}
}
public virtual void AddToGUIUpdateList()
{
if (controlled == this)
{
CharacterHUD.AddToGUIUpdateList(this);
CharacterHealth.AddToGUIUpdateList();
}
}
public void DoVisibilityCheck(Camera cam)
{
IsVisible = false;
if (!Enabled || AnimController.SimplePhysicsEnabled) { return; }
foreach (Limb limb in AnimController.Limbs)
{
float maxExtent = ConvertUnits.ToDisplayUnits(limb.body.GetMaxExtent());
if (limb.LightSource != null) { maxExtent = Math.Max(limb.LightSource.Range, maxExtent); }
if (limb.body.DrawPosition.X < cam.WorldView.X - maxExtent || limb.body.DrawPosition.X > cam.WorldView.Right + maxExtent) { continue; }
if (limb.body.DrawPosition.Y < cam.WorldView.Y - cam.WorldView.Height - maxExtent || limb.body.DrawPosition.Y > cam.WorldView.Y + maxExtent) { continue; }
IsVisible = true;
return;
}
}
public void Draw(SpriteBatch spriteBatch, Camera cam)
{
if (!Enabled) { return; }
AnimController.Draw(spriteBatch, cam);
}
public void DrawHUD(SpriteBatch spriteBatch, Camera cam, bool drawHealth = true)
{
CharacterHUD.Draw(spriteBatch, this, cam);
if (drawHealth) CharacterHealth.DrawHUD(spriteBatch);
}
public virtual void DrawFront(SpriteBatch spriteBatch, Camera cam)
{
if (!Enabled) { return; }
if (GameMain.DebugDraw)
{
AnimController.DebugDraw(spriteBatch);
}
if (GUI.DisableHUD) return;
if (Controlled != null &&
Controlled != this &&
Submarine != null &&
Controlled.Submarine == Submarine &&
GameMain.Config.LosMode != LosMode.None)
{
float yPos = Controlled.AnimController.FloorY - 1.5f;
if (Controlled.AnimController.Stairs != null)
{
yPos = Controlled.AnimController.Stairs.SimPosition.Y - Controlled.AnimController.Stairs.RectHeight * 0.5f;
}
foreach (var ladder in Ladder.List)
{
if (CanInteractWith(ladder.Item) && Controlled.CanInteractWith(ladder.Item))
{
float xPos = ladder.Item.SimPosition.X;
if (Math.Abs(xPos - SimPosition.X) < 3.0)
{
yPos = ladder.Item.SimPosition.Y - ladder.Item.RectHeight * 0.5f;
}
break;
}
}
if (AnimController.FloorY < yPos) { return; }
}
Vector2 pos = DrawPosition;
pos.Y += hudInfoHeight;
if (CurrentHull != null && DrawPosition.Y > CurrentHull.WorldRect.Y - 130.0f)
{
float lowerAmount = DrawPosition.Y - (CurrentHull.WorldRect.Y - 130.0f);
hudInfoHeight = MathHelper.Lerp(hudInfoHeight, 100.0f - lowerAmount, 0.1f);
hudInfoHeight = Math.Max(hudInfoHeight, 20.0f);
}
else
{
hudInfoHeight = MathHelper.Lerp(hudInfoHeight, 100.0f, 0.1f);
}
pos.Y = -pos.Y;
if (speechBubbleTimer > 0.0f)
{
GUI.SpeechBubbleIcon.Draw(spriteBatch, pos - Vector2.UnitY * 30,
speechBubbleColor * Math.Min(speechBubbleTimer, 1.0f), 0.0f,
Math.Min(speechBubbleTimer, 1.0f));
}
if (this == controlled)
{
if (DebugDrawInteract)
{
Vector2 cursorPos = cam.ScreenToWorld(PlayerInput.MousePosition);
cursorPos.Y = -cursorPos.Y;
foreach (Item item in debugInteractablesAtCursor)
{
GUI.DrawLine(spriteBatch, cursorPos,
new Vector2(item.DrawPosition.X, -item.DrawPosition.Y), Color.LightGreen, width: 4);
}
foreach (Item item in debugInteractablesInRange)
{
GUI.DrawLine(spriteBatch, new Vector2(DrawPosition.X, -DrawPosition.Y),
new Vector2(item.DrawPosition.X, -item.DrawPosition.Y), Color.White * 0.1f, width: 4);
}
foreach (Pair<Item, float> item in debugInteractablesNearCursor)
{
GUI.DrawLine(spriteBatch,
cursorPos,
new Vector2(item.First.DrawPosition.X, -item.First.DrawPosition.Y),
ToolBox.GradientLerp(item.Second, GUI.Style.Red, GUI.Style.Orange, GUI.Style.Green), width: 2);
}
}
return;
}
float hoverRange = 300.0f;
float fadeOutRange = 200.0f;
float cursorDist = Vector2.Distance(WorldPosition, cam.ScreenToWorld(PlayerInput.MousePosition));
float hudInfoAlpha = MathHelper.Clamp(1.0f - (cursorDist - (hoverRange - fadeOutRange)) / fadeOutRange, 0.2f, 1.0f);
if (!GUI.DisableCharacterNames && hudInfoVisible && info != null &&
(controlled == null || this != controlled.FocusedCharacter))
{
string name = Info.DisplayName;
if (controlled == null && name != Info.Name) name += " " + TextManager.Get("Disguised");
Vector2 namePos = new Vector2(pos.X, pos.Y - 10.0f - (5.0f / cam.Zoom)) - GUI.Font.MeasureString(name) * 0.5f / cam.Zoom;
Vector2 screenSize = new Vector2(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
Vector2 viewportSize = new Vector2(cam.WorldView.Width, cam.WorldView.Height);
namePos.X -= cam.WorldView.X; namePos.Y += cam.WorldView.Y;
namePos *= screenSize / viewportSize;
namePos.X = (float)Math.Floor(namePos.X); namePos.Y = (float)Math.Floor(namePos.Y);
namePos *= viewportSize / screenSize;
namePos.X += cam.WorldView.X; namePos.Y -= cam.WorldView.Y;
Color nameColor = Color.White;
if (Controlled != null && TeamID != Controlled.TeamID)
{
nameColor = TeamID == TeamType.FriendlyNPC ? Color.SkyBlue : GUI.Style.Red;
}
GUI.Font.DrawString(spriteBatch, name, namePos + new Vector2(1.0f / cam.Zoom, 1.0f / cam.Zoom), Color.Black, 0.0f, Vector2.Zero, 1.0f / cam.Zoom, SpriteEffects.None, 0.001f);
GUI.Font.DrawString(spriteBatch, name, namePos, nameColor * hudInfoAlpha, 0.0f, Vector2.Zero, 1.0f / cam.Zoom, SpriteEffects.None, 0.0f);
if (GameMain.DebugDraw)
{
GUI.Font.DrawString(spriteBatch, ID.ToString(), namePos - new Vector2(0.0f, 20.0f), Color.White);
}
}
if (IsDead) return;
if (CharacterHealth.DisplayedVitality < MaxVitality * 0.98f && hudInfoVisible)
{
hudInfoAlpha = Math.Max(hudInfoAlpha, Math.Min(CharacterHealth.DamageOverlayTimer, 1.0f));
Vector2 healthBarPos = new Vector2(pos.X - 50, -pos.Y);
GUI.DrawProgressBar(spriteBatch, healthBarPos, new Vector2(100.0f, 15.0f),
CharacterHealth.DisplayedVitality / MaxVitality,
Color.Lerp(GUI.Style.Red, GUI.Style.Green, CharacterHealth.DisplayedVitality / MaxVitality) * 0.8f * hudInfoAlpha,
new Color(0.5f, 0.57f, 0.6f, 1.0f) * hudInfoAlpha);
}
}
/// <summary>
/// Creates a progress bar that's "linked" to the specified object (or updates an existing one if there's one already linked to the object)
/// The progress bar will automatically fade out after 1 sec if the method hasn't been called during that time
/// </summary>
public HUDProgressBar UpdateHUDProgressBar(object linkedObject, Vector2 worldPosition, float progress, Color emptyColor, Color fullColor)
{
if (controlled != this) return null;
if (!hudProgressBars.TryGetValue(linkedObject, out HUDProgressBar progressBar))
{
progressBar = new HUDProgressBar(worldPosition, Submarine, emptyColor, fullColor);
hudProgressBars.Add(linkedObject, progressBar);
}
progressBar.WorldPosition = worldPosition;
progressBar.FadeTimer = Math.Max(progressBar.FadeTimer, 1.0f);
progressBar.Progress = progress;
return progressBar;
}
private SoundChannel soundChannel;
public void PlaySound(CharacterSound.SoundType soundType)
{
if (sounds == null || sounds.Count == 0) { return; }
if (soundChannel != null && soundChannel.IsPlaying) { return; }
var matchingSounds = sounds.Where(s =>
s.Type == soundType &&
(s.Gender == Gender.None || (info != null && info.Gender == s.Gender)));
if (!matchingSounds.Any()) { return; }
var matchingSoundsList = matchingSounds.ToList();
var selectedSound = matchingSoundsList[Rand.Int(matchingSoundsList.Count)];
soundChannel = SoundPlayer.PlaySound(selectedSound.Sound, AnimController.WorldPosition, selectedSound.Volume, selectedSound.Range, CurrentHull);
soundTimer = soundInterval;
}
public void AddActiveObjectiveEntity(Entity entity, Sprite sprite, Color? color = null)
{
if (activeObjectiveEntities.Any(aoe => aoe.Entity == entity)) return;
ObjectiveEntity objectiveEntity = new ObjectiveEntity(entity, sprite, color);
activeObjectiveEntities.Add(objectiveEntity);
}
public void RemoveActiveObjectiveEntity(Entity entity)
{
ObjectiveEntity found = activeObjectiveEntities.Find(aoe => aoe.Entity == entity);
if (found == null) return;
activeObjectiveEntities.Remove(found);
}
partial void ImplodeFX()
{
Vector2 centerOfMass = AnimController.GetCenterOfMass();
SoundPlayer.PlaySound("implode", WorldPosition);
for (int i = 0; i < 10; i++)
{
Particle p = GameMain.ParticleManager.CreateParticle("waterblood",
WorldPosition + Rand.Vector(5.0f),
Rand.Vector(10.0f));
if (p != null) p.Size *= 2.0f;
GameMain.ParticleManager.CreateParticle("bubbles",
ConvertUnits.ToDisplayUnits(centerOfMass) + Rand.Vector(5.0f),
new Vector2(Rand.Range(-50f, 50f), Rand.Range(-100f, 50f)));
GameMain.ParticleManager.CreateParticle("gib",
WorldPosition + Rand.Vector(Rand.Range(0.0f, 50.0f)),
Rand.Range(0.0f, MathHelper.TwoPi),
Rand.Range(200.0f, 700.0f), null);
}
for (int i = 0; i < 30; i++)
{
GameMain.ParticleManager.CreateParticle("heavygib",
WorldPosition + Rand.Vector(Rand.Range(0.0f, 50.0f)),
Rand.Range(0.0f, MathHelper.TwoPi),
Rand.Range(50.0f, 500.0f), null);
}
}
}
}

View File

@@ -0,0 +1,462 @@
using Barotrauma.Items.Components;
using FarseerPhysics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
class CharacterHUD
{
private static Dictionary<Entity, int> orderIndicatorCount = new Dictionary<Entity, int>();
const float ItemOverlayDelay = 1.0f;
private static Item focusedItem;
private static float focusedItemOverlayTimer;
private static List<Item> brokenItems = new List<Item>();
private static float brokenItemsCheckTimer;
private static Dictionary<string, string> cachedHudTexts = new Dictionary<string, string>();
private static GUIFrame hudFrame;
public static GUIFrame HUDFrame
{
get
{
if (hudFrame == null)
{
hudFrame = new GUIFrame(new RectTransform(Vector2.One, GUI.Canvas), style: null)
{
CanBeFocused = false
};
}
return hudFrame;
}
}
private static bool ShouldDrawInventory(Character character)
{
return
character?.Inventory != null &&
character.AllowInput &&
!character.LockHands &&
character.SelectedConstruction?.GetComponent<Controller>()?.User != character;
}
private static string GetCachedHudText(string textTag, string keyBind)
{
if (cachedHudTexts.TryGetValue(textTag + keyBind, out string text))
{
return text;
}
text = TextManager.GetWithVariable(textTag, "[key]", keyBind);
cachedHudTexts.Add(textTag + keyBind, text);
return text;
}
public static void AddToGUIUpdateList(Character character)
{
if (GUI.DisableHUD) return;
if (!character.IsUnconscious && character.Stun <= 0.0f)
{
if (character.Inventory != null)
{
for (int i = 0; i < character.Inventory.Items.Length - 1; i++)
{
var item = character.Inventory.Items[i];
if (item == null || character.Inventory.SlotTypes[i] == InvSlotType.Any) continue;
foreach (ItemComponent ic in item.Components)
{
if (ic.DrawHudWhenEquipped) ic.AddToGUIUpdateList();
}
}
}
if (character.IsHumanoid && character.SelectedCharacter != null)
{
character.SelectedCharacter.CharacterHealth.AddToGUIUpdateList();
}
}
HUDFrame.AddToGUIUpdateList();
}
public static void Update(float deltaTime, Character character, Camera cam)
{
if (GUI.DisableHUD) { return; }
if (!character.IsUnconscious && character.Stun <= 0.0f)
{
if (character.Info != null && !character.ShouldLockHud() && character.SelectedCharacter == null)
{
bool mouseOnPortrait = HUDLayoutSettings.BottomRightInfoArea.Contains(PlayerInput.MousePosition) && GUI.MouseOn == null;
if (mouseOnPortrait && PlayerInput.PrimaryMouseButtonClicked())
{
CharacterHealth.OpenHealthWindow = character.CharacterHealth;
}
}
if (character.Inventory != null)
{
if (!LockInventory(character))
{
character.Inventory.Update(deltaTime, cam);
}
for (int i = 0; i < character.Inventory.Items.Length - 1; i++)
{
var item = character.Inventory.Items[i];
if (item == null || character.Inventory.SlotTypes[i] == InvSlotType.Any) continue;
foreach (ItemComponent ic in item.Components)
{
if (ic.DrawHudWhenEquipped) ic.UpdateHUD(character, deltaTime, cam);
}
}
}
if (character.IsHumanoid && character.SelectedCharacter != null && character.SelectedCharacter.Inventory != null)
{
if (character.SelectedCharacter.CanInventoryBeAccessed)
{
character.SelectedCharacter.Inventory.Update(deltaTime, cam);
}
character.SelectedCharacter.CharacterHealth.UpdateHUD(deltaTime);
}
Inventory.UpdateDragging();
}
if (focusedItem != null)
{
if (character.FocusedItem != null)
{
focusedItemOverlayTimer = Math.Min(focusedItemOverlayTimer + deltaTime, ItemOverlayDelay + 1.0f);
}
else
{
focusedItemOverlayTimer = Math.Max(focusedItemOverlayTimer - deltaTime, 0.0f);
if (focusedItemOverlayTimer <= 0.0f) focusedItem = null;
}
}
if (brokenItemsCheckTimer > 0.0f)
{
brokenItemsCheckTimer -= deltaTime;
}
else
{
brokenItems.Clear();
brokenItemsCheckTimer = 1.0f;
foreach (Item item in Item.ItemList)
{
if (!item.Repairables.Any(r => item.ConditionPercentage <= r.AIRepairThreshold)) { continue; }
if (Submarine.VisibleEntities != null && !Submarine.VisibleEntities.Contains(item)) { continue; }
Vector2 diff = item.WorldPosition - character.WorldPosition;
if (Submarine.CheckVisibility(character.SimPosition, character.SimPosition + ConvertUnits.ToSimUnits(diff)) == null)
{
brokenItems.Add(item);
}
}
}
}
public static void Draw(SpriteBatch spriteBatch, Character character, Camera cam)
{
if (GUI.DisableHUD) { return; }
character.CharacterHealth.Alignment = Alignment.Right;
if (GameMain.GameSession?.CrewManager != null)
{
orderIndicatorCount.Clear();
foreach (Pair<Order, float> timedOrder in GameMain.GameSession.CrewManager.ActiveOrders)
{
DrawOrderIndicator(spriteBatch, cam, character, timedOrder.First, MathHelper.Clamp(timedOrder.Second / 10.0f, 0.2f, 1.0f));
}
if (character.CurrentOrder != null)
{
DrawOrderIndicator(spriteBatch, cam, character, character.CurrentOrder, 1.0f);
}
}
foreach (Character.ObjectiveEntity objectiveEntity in character.ActiveObjectiveEntities)
{
DrawObjectiveIndicator(spriteBatch, cam, character, objectiveEntity, 1.0f);
}
foreach (Item brokenItem in brokenItems)
{
float dist = Vector2.Distance(character.WorldPosition, brokenItem.WorldPosition);
Vector2 drawPos = brokenItem.DrawPosition;
float alpha = Math.Min((1000.0f - dist) / 1000.0f * 2.0f, 1.0f);
if (alpha <= 0.0f) continue;
GUI.DrawIndicator(spriteBatch, drawPos, cam, 100.0f, GUI.BrokenIcon,
Color.Lerp(GUI.Style.Red, GUI.Style.Orange * 0.5f, brokenItem.Condition / brokenItem.MaxCondition) * alpha);
}
if (!character.IsUnconscious && character.Stun <= 0.0f)
{
if (character.FocusedCharacter != null && character.FocusedCharacter.CanBeSelected)
{
DrawCharacterHoverTexts(spriteBatch, cam, character);
}
float circleSize;
if (character.FocusedItem != null)
{
if (focusedItem != character.FocusedItem)
{
focusedItemOverlayTimer = Math.Min(1.0f, focusedItemOverlayTimer);
}
focusedItem = character.FocusedItem;
}
if (focusedItem != null && focusedItemOverlayTimer > ItemOverlayDelay)
{
Vector2 circlePos = cam.WorldToScreen(focusedItem.DrawPosition);
circleSize = Math.Max(focusedItem.Rect.Width, focusedItem.Rect.Height) * 1.5f;
circleSize = MathHelper.Clamp(circleSize, 45.0f, 100.0f) * Math.Min((focusedItemOverlayTimer - 1.0f) * 5.0f, 1.0f);
if (circleSize > 0.0f)
{
Vector2 scale = new Vector2(circleSize / GUI.Style.FocusIndicator.FrameSize.X);
GUI.Style.FocusIndicator.Draw(spriteBatch,
(int)((focusedItemOverlayTimer - 1.0f) * GUI.Style.FocusIndicator.FrameCount * 3.0f),
circlePos,
Color.LightBlue * 0.3f,
origin: GUI.Style.FocusIndicator.FrameSize.ToVector2() / 2,
rotate: (float)Timing.TotalTime,
scale: scale);
}
if (!GUI.DisableItemHighlights && !Inventory.DraggingItemToWorld)
{
var hudTexts = focusedItem.GetHUDTexts(character);
int dir = Math.Sign(focusedItem.WorldPosition.X - character.WorldPosition.X);
Vector2 textSize = GUI.Font.MeasureString(focusedItem.Name);
Vector2 largeTextSize = GUI.SubHeadingFont.MeasureString(focusedItem.Name);
Vector2 startPos = cam.WorldToScreen(focusedItem.DrawPosition);
startPos.Y -= (hudTexts.Count + 1) * textSize.Y;
if (focusedItem.Sprite != null)
{
startPos.X += (int)(circleSize * 0.4f * dir);
startPos.Y -= (int)(circleSize * 0.4f);
}
Vector2 textPos = startPos;
if (dir == -1) { textPos.X -= largeTextSize.X; }
float alpha = MathHelper.Clamp((focusedItemOverlayTimer - ItemOverlayDelay) * 2.0f, 0.0f, 1.0f);
GUI.DrawString(spriteBatch, textPos, focusedItem.Name, GUI.Style.TextColor * alpha, Color.Black * alpha * 0.7f, 2, font: GUI.SubHeadingFont);
startPos.X += dir * 10.0f * GUI.Scale;
textPos.X += dir * 10.0f * GUI.Scale;
textPos.Y += largeTextSize.Y;
foreach (ColoredText coloredText in hudTexts)
{
if (dir == -1) textPos.X = (int)(startPos.X - GUI.SmallFont.MeasureString(coloredText.Text).X);
GUI.DrawString(spriteBatch, textPos, coloredText.Text, coloredText.Color * alpha, Color.Black * alpha * 0.7f, 2, GUI.SmallFont);
textPos.Y += textSize.Y;
}
}
}
foreach (HUDProgressBar progressBar in character.HUDProgressBars.Values)
{
progressBar.Draw(spriteBatch, cam);
}
}
if (character.SelectedConstruction != null &&
(character.CanInteractWith(Character.Controlled.SelectedConstruction) || Screen.Selected == GameMain.SubEditorScreen))
{
character.SelectedConstruction.DrawHUD(spriteBatch, cam, Character.Controlled);
}
if (character.Inventory != null)
{
for (int i = 0; i < character.Inventory.Items.Length - 1; i++)
{
var item = character.Inventory.Items[i];
if (item == null || character.Inventory.SlotTypes[i] == InvSlotType.Any) continue;
foreach (ItemComponent ic in item.Components)
{
if (ic.DrawHudWhenEquipped) ic.DrawHUD(spriteBatch, character);
}
}
}
bool mouseOnPortrait = false;
if (character.Stun <= 0.1f && !character.IsDead)
{
if (CharacterHealth.OpenHealthWindow == null && character.SelectedCharacter == null)
{
if (character.Info != null && !character.ShouldLockHud())
{
character.Info.DrawBackground(spriteBatch);
character.Info.DrawJobIcon(spriteBatch,
new Rectangle(
(int)(HUDLayoutSettings.BottomRightInfoArea.X + HUDLayoutSettings.BottomRightInfoArea.Width * 0.05f),
(int)(HUDLayoutSettings.BottomRightInfoArea.Y + HUDLayoutSettings.BottomRightInfoArea.Height * 0.1f),
(int)(HUDLayoutSettings.BottomRightInfoArea.Width / 2),
(int)(HUDLayoutSettings.BottomRightInfoArea.Height * 0.7f)));
character.Info.DrawPortrait(spriteBatch, HUDLayoutSettings.PortraitArea.Location.ToVector2(), new Vector2(-12 * GUI.Scale, 4 * GUI.Scale), targetWidth: HUDLayoutSettings.PortraitArea.Width, true);
}
mouseOnPortrait = HUDLayoutSettings.BottomRightInfoArea.Contains(PlayerInput.MousePosition) && !character.ShouldLockHud();
if (mouseOnPortrait)
{
GUI.UIGlow.Draw(spriteBatch, HUDLayoutSettings.BottomRightInfoArea, GUI.Style.Green * 0.5f);
}
}
if (ShouldDrawInventory(character))
{
character.Inventory.Locked = LockInventory(character);
character.Inventory.DrawOwn(spriteBatch);
character.Inventory.CurrentLayout = CharacterHealth.OpenHealthWindow == null && character.SelectedCharacter == null ?
CharacterInventory.Layout.Default :
CharacterInventory.Layout.Right;
}
}
if (!character.IsUnconscious && character.Stun <= 0.0f)
{
if (character.IsHumanoid && character.SelectedCharacter != null && character.SelectedCharacter.Inventory != null)
{
if (character.SelectedCharacter.CanInventoryBeAccessed)
{
///character.Inventory.CurrentLayout = Alignment.Left;
character.SelectedCharacter.Inventory.CurrentLayout = CharacterInventory.Layout.Left;
character.SelectedCharacter.Inventory.DrawOwn(spriteBatch);
}
else
{
//character.Inventory.CurrentLayout = (CharacterHealth.OpenHealthWindow == null) ? Alignment.Center : Alignment.Left;
}
if (CharacterHealth.OpenHealthWindow == character.SelectedCharacter.CharacterHealth)
{
character.SelectedCharacter.CharacterHealth.Alignment = Alignment.Left;
character.SelectedCharacter.CharacterHealth.DrawStatusHUD(spriteBatch);
}
}
else if (character.Inventory != null)
{
//character.Inventory.CurrentLayout = (CharacterHealth.OpenHealthWindow == null) ? Alignment.Center : Alignment.Left;
}
}
if (mouseOnPortrait)
{
GUIComponent.DrawToolTip(
spriteBatch,
character.Info?.Job == null ? character.DisplayName : character.Name + " (" + character.Info.Job.Name + ")",
HUDLayoutSettings.PortraitArea);
}
}
private static void DrawCharacterHoverTexts(SpriteBatch spriteBatch, Camera cam, Character character)
{
foreach (Item item in character.Inventory.Items)
{
var statusHUD = item?.GetComponent<StatusHUD>();
if (statusHUD != null && statusHUD.IsActive && statusHUD.VisibleCharacters.Contains(character.FocusedCharacter))
{
return;
}
}
Vector2 startPos = character.DrawPosition + (character.FocusedCharacter.DrawPosition - character.DrawPosition) * 0.7f;
startPos = cam.WorldToScreen(startPos);
string focusName = character.FocusedCharacter.DisplayName;
if (character.FocusedCharacter.Info != null)
{
focusName = character.FocusedCharacter.Info.DisplayName;
}
Vector2 textPos = startPos;
Vector2 textSize = GUI.Font.MeasureString(focusName);
Vector2 largeTextSize = GUI.SubHeadingFont.MeasureString(focusName);
textPos -= new Vector2(textSize.X / 2, textSize.Y);
Color nameColor = GUI.Style.TextColor;
if (character.TeamID != character.FocusedCharacter.TeamID)
{
nameColor = character.FocusedCharacter.TeamID == Character.TeamType.FriendlyNPC ? Color.SkyBlue : GUI.Style.Red;
}
GUI.DrawString(spriteBatch, textPos, focusName, nameColor, Color.Black * 0.7f, 2, GUI.SubHeadingFont);
textPos.X += 10.0f * GUI.Scale;
textPos.Y += GUI.SubHeadingFont.MeasureString(focusName).Y;
if (character.FocusedCharacter.CanBeDragged)
{
GUI.DrawString(spriteBatch, textPos, GetCachedHudText("GrabHint", GameMain.Config.KeyBindText(InputType.Grab)),
GUI.Style.Green, Color.Black, 2, GUI.SmallFont);
textPos.Y += largeTextSize.Y;
}
if (character.FocusedCharacter.CharacterHealth.UseHealthWindow && character.CanInteractWith(character.FocusedCharacter, 160f, false))
{
GUI.DrawString(spriteBatch, textPos, GetCachedHudText("HealHint", GameMain.Config.KeyBindText(InputType.Health)),
GUI.Style.Green, Color.Black, 2, GUI.SmallFont);
textPos.Y += textSize.Y;
}
if (!string.IsNullOrEmpty(character.FocusedCharacter.customInteractHUDText))
{
GUI.DrawString(spriteBatch, textPos, character.FocusedCharacter.customInteractHUDText, GUI.Style.Green, Color.Black, 2, GUI.SmallFont);
textPos.Y += textSize.Y;
}
}
private static bool LockInventory(Character character)
{
if (character?.Inventory == null || !character.AllowInput || character.LockHands) { return true; }
return character.ShouldLockHud();
}
private static void DrawOrderIndicator(SpriteBatch spriteBatch, Camera cam, Character character, Order order, float iconAlpha = 1.0f)
{
if (order.TargetAllCharacters)
{
if (order.OrderGiver != character && !order.HasAppropriateJob(character))
{
return;
}
}
Entity target = order.ConnectedController != null ? order.ConnectedController.Item : order.TargetEntity;
if (target == null) { return; }
//don't show the indicator if far away and not inside the same sub
//prevents exploiting the indicators in locating the sub
if (character.Submarine != target.Submarine &&
Vector2.DistanceSquared(character.WorldPosition, target.WorldPosition) > 1000.0f * 1000.0f)
{
return;
}
if (!orderIndicatorCount.ContainsKey(target)) { orderIndicatorCount.Add(target, 0); }
Vector2 drawPos = target.DrawPosition + Vector2.UnitX * order.SymbolSprite.size.X * 1.5f * orderIndicatorCount[target];
GUI.DrawIndicator(spriteBatch, drawPos, cam, 100.0f, order.SymbolSprite, order.Color * iconAlpha);
orderIndicatorCount[target] = orderIndicatorCount[target] + 1;
}
private static void DrawObjectiveIndicator(SpriteBatch spriteBatch, Camera cam, Character character, Character.ObjectiveEntity objectiveEntity, float iconAlpha = 1.0f)
{
if (objectiveEntity == null) return;
Vector2 drawPos = objectiveEntity.Entity.WorldPosition;// + Vector2.UnitX * objectiveEntity.Sprite.size.X * 1.5f;
GUI.DrawIndicator(spriteBatch, drawPos, cam, 100.0f, objectiveEntity.Sprite, objectiveEntity.Color * iconAlpha);
}
}
}

View File

@@ -0,0 +1,331 @@
using Barotrauma.Extensions;
using Barotrauma.Networking;
using System;
using System.Linq;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace Barotrauma
{
partial class CharacterInfo
{
private static Sprite infoAreaPortraitBG;
public static void Init()
{
infoAreaPortraitBG = GUI.Style.GetComponentStyle("InfoAreaPortraitBG")?.Sprites[GUIComponent.ComponentState.None][0].Sprite;
new Sprite("Content/UI/InventoryUIAtlas.png", new Rectangle(833, 298, 142, 98), null, 0);
}
public GUIFrame CreateInfoFrame(GUIFrame frame)
{
var paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), frame.RectTransform, Anchor.TopCenter) { RelativeOffset = new Vector2(0.0f, 0.1f) })
{
Stretch = true,
RelativeSpacing = 0.03f
};
var headerArea = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.4f), paddedFrame.RectTransform), isHorizontal: true)
{
RelativeSpacing = 0.05f,
Stretch = true
};
new GUICustomComponent(new RectTransform(new Vector2(0.25f, 1.0f), headerArea.RectTransform),
onDraw: (sb, component) => DrawIcon(sb, component.Rect.Center.ToVector2(), targetAreaSize: component.Rect.Size.ToVector2()));
ScalableFont font = paddedFrame.Rect.Width < 280 ? GUI.SmallFont : GUI.Font;
var headerTextArea = new GUILayoutGroup(new RectTransform(new Vector2(0.75f, 1.0f), headerArea.RectTransform))
{
RelativeSpacing = 0.05f,
Stretch = true
};
Color? nameColor = null;
if (Job != null) { nameColor = Job.Prefab.UIColor; }
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), headerTextArea.RectTransform),
Name, textColor: nameColor, font: GUI.LargeFont)
{
Padding = Vector4.Zero,
AutoScaleHorizontal = true
};
if (Job != null)
{
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), headerTextArea.RectTransform),
Job.Name, textColor: Job.Prefab.UIColor, font: font);
}
if (personalityTrait != null)
{
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), headerTextArea.RectTransform),
TextManager.AddPunctuation(':', TextManager.Get("PersonalityTrait"), TextManager.Get("personalitytrait." + personalityTrait.Name.Replace(" ", ""))), font: font);
}
//spacing
new GUIFrame(new RectTransform(new Vector2(1.0f, 0.05f), paddedFrame.RectTransform), style: null);
if (Job != null)
{
var skills = Job.Skills;
skills.Sort((s1, s2) => -s1.Level.CompareTo(s2.Level));
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedFrame.RectTransform),
TextManager.Get("Skills") + ":", font: font);
foreach (Skill skill in skills)
{
Color textColor = Color.White * (0.5f + skill.Level / 200.0f);
var skillName = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedFrame.RectTransform),
TextManager.Get("SkillName." + skill.Identifier), textColor: textColor, font: font);
new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), skillName.RectTransform),
((int)skill.Level).ToString(), textColor: textColor, font: font, textAlignment: Alignment.CenterRight);
}
}
return frame;
}
public GUIFrame CreateCharacterFrame(GUIComponent parent, string text, object userData)
{
GUIFrame frame = new GUIFrame(new RectTransform(new Point(parent.Rect.Width, 40), parent.RectTransform) { IsFixedSize = false }, "ListBoxElement")
{
UserData = userData
};
Color? textColor = null;
if (Job != null) { textColor = Job.Prefab.UIColor; }
GUITextBlock textBlock = new GUITextBlock(new RectTransform(Vector2.One, frame.RectTransform, Anchor.CenterLeft) { AbsoluteOffset = new Point(40, 0) }, text, textColor: textColor, font: GUI.SmallFont);
new GUICustomComponent(new RectTransform(new Point(frame.Rect.Height, frame.Rect.Height), frame.RectTransform, Anchor.CenterLeft) { IsFixedSize = false },
onDraw: (sb, component) => DrawIcon(sb, component.Rect.Center.ToVector2(), targetAreaSize: component.Rect.Size.ToVector2()));
return frame;
}
partial void OnSkillChanged(string skillIdentifier, float prevLevel, float newLevel, Vector2 textPopupPos)
{
if (newLevel - prevLevel > 0.1f)
{
GUI.AddMessage(
"+" + ((int)((newLevel - prevLevel) * 100.0f)).ToString() + " XP",
GUI.Style.Green,
textPopupPos,
Vector2.UnitY * 10.0f);
}
else if (prevLevel % 0.1f > 0.05f && newLevel % 0.1f < 0.05f)
{
GUI.AddMessage(
"+10 XP",
GUI.Style.Green,
textPopupPos,
Vector2.UnitY * 10.0f);
}
if ((int)newLevel > (int)prevLevel)
{
GUI.AddMessage(
TextManager.GetWithVariables("SkillIncreased", new string[3] { "[name]", "[skillname]", "[newlevel]" },
new string[3] { Name, TextManager.Get("SkillName." + skillIdentifier), ((int)newLevel).ToString() },
new bool[3] { false, true, false }), GUI.Style.Green);
}
}
partial void LoadAttachmentSprites(bool omitJob)
{
if (attachmentSprites == null)
{
attachmentSprites = new List<WearableSprite>();
}
if (!IsAttachmentsLoaded)
{
LoadHeadAttachments();
}
FaceAttachment?.Elements("sprite").ForEach(s => attachmentSprites.Add(new WearableSprite(s, WearableType.FaceAttachment)));
BeardElement?.Elements("sprite").ForEach(s => attachmentSprites.Add(new WearableSprite(s, WearableType.Beard)));
MoustacheElement?.Elements("sprite").ForEach(s => attachmentSprites.Add(new WearableSprite(s, WearableType.Moustache)));
HairElement?.Elements("sprite").ForEach(s => attachmentSprites.Add(new WearableSprite(s, WearableType.Hair)));
if (omitJob)
{
JobPrefab.NoJobElement?.Element("PortraitClothing")?.Elements("sprite").ForEach(s => attachmentSprites.Add(new WearableSprite(s, WearableType.JobIndicator)));
}
else
{
Job?.Prefab.ClothingElement?.Elements("sprite").ForEach(s => attachmentSprites.Add(new WearableSprite(s, WearableType.JobIndicator)));
}
}
// Doesn't work if the head's source rect does not start at 0,0.
public static Point CalculateOffset(Sprite sprite, Point offset) => sprite.SourceRect.Size * offset;
public void CalculateHeadPosition(Sprite sprite)
{
if (sprite == null) { return; }
if (Head.SheetIndex == null) { return; }
Point location = CalculateOffset(sprite, Head.SheetIndex.Value.ToPoint());
sprite.SourceRect = new Rectangle(location, sprite.SourceRect.Size);
}
public void DrawBackground(SpriteBatch spriteBatch)
{
if (infoAreaPortraitBG == null) { return; }
infoAreaPortraitBG.Draw(spriteBatch, HUDLayoutSettings.BottomRightInfoArea.Location.ToVector2(), Color.White, Vector2.Zero, 0.0f,
scale: new Vector2(
HUDLayoutSettings.BottomRightInfoArea.Width / (float)infoAreaPortraitBG.SourceRect.Width,
HUDLayoutSettings.BottomRightInfoArea.Height / (float)infoAreaPortraitBG.SourceRect.Height));
}
public void DrawPortrait(SpriteBatch spriteBatch, Vector2 screenPos, Vector2 offset, float targetWidth, bool flip = false)
{
if (Portrait != null)
{
// Scale down the head sprite 10%
float scale = targetWidth * 0.9f / Portrait.size.X;
if (Head.SheetIndex.HasValue)
{
Portrait.SourceRect = new Rectangle(CalculateOffset(Portrait, Head.SheetIndex.Value.ToPoint()), Portrait.SourceRect.Size);
}
Portrait.Draw(spriteBatch, screenPos + offset, Color.White, Portrait.Origin, scale: scale, spriteEffect: flip ? SpriteEffects.FlipHorizontally : SpriteEffects.None);
if (AttachmentSprites != null)
{
float depthStep = 0.000001f;
foreach (var attachment in AttachmentSprites)
{
DrawAttachmentSprite(spriteBatch, attachment, Portrait, screenPos + offset, scale, depthStep, flip ? SpriteEffects.FlipHorizontally : SpriteEffects.None);
depthStep += depthStep;
}
}
}
}
public void DrawIcon(SpriteBatch spriteBatch, Vector2 screenPos, Vector2 targetAreaSize)
{
var headSprite = HeadSprite;
if (headSprite != null)
{
float scale = Math.Min(targetAreaSize.X / headSprite.size.X, targetAreaSize.Y / headSprite.size.Y);
if (Head.SheetIndex.HasValue)
{
headSprite.SourceRect = new Rectangle(CalculateOffset(headSprite, Head.SheetIndex.Value.ToPoint()), headSprite.SourceRect.Size);
}
headSprite.Draw(spriteBatch, screenPos, scale: scale);
if (AttachmentSprites != null)
{
float depthStep = 0.000001f;
foreach (var attachment in AttachmentSprites)
{
DrawAttachmentSprite(spriteBatch, attachment, headSprite, screenPos, scale, depthStep);
depthStep += depthStep;
}
}
}
}
public void DrawJobIcon(SpriteBatch spriteBatch, Vector2 pos, float scale = 1.0f)
{
var icon = Job?.Prefab?.Icon;
if (icon == null) { return; }
icon.Draw(spriteBatch, pos, Job.Prefab.UIColor, scale: scale);
}
public void DrawJobIcon(SpriteBatch spriteBatch, Rectangle area)
{
var icon = Job?.Prefab?.Icon;
if (icon == null) { return; }
icon.Draw(spriteBatch,
area.Center.ToVector2(),
Job.Prefab.UIColor,
scale: Math.Min(area.Width / (float)icon.SourceRect.Width, area.Height / (float)icon.SourceRect.Height));
}
private void DrawAttachmentSprite(SpriteBatch spriteBatch, WearableSprite attachment, Sprite head, Vector2 drawPos, float scale, float depthStep, SpriteEffects spriteEffects = SpriteEffects.None)
{
if (attachment.InheritSourceRect)
{
if (attachment.SheetIndex.HasValue)
{
attachment.Sprite.SourceRect = new Rectangle(CalculateOffset(head, attachment.SheetIndex.Value), head.SourceRect.Size);
}
else if (Head.SheetIndex.HasValue)
{
attachment.Sprite.SourceRect = new Rectangle(CalculateOffset(head, Head.SheetIndex.Value.ToPoint()), head.SourceRect.Size);
}
else
{
attachment.Sprite.SourceRect = head.SourceRect;
}
}
Vector2 origin = attachment.Sprite.Origin;
if (attachment.InheritOrigin)
{
origin = head.Origin;
attachment.Sprite.Origin = origin;
}
else
{
origin = attachment.Sprite.Origin;
}
float depth = attachment.Sprite.Depth;
if (attachment.InheritLimbDepth)
{
depth = head.Depth - depthStep;
}
attachment.Sprite.Draw(spriteBatch, drawPos, Color.White, origin, rotate: 0, scale: scale, depth: depth, spriteEffect: spriteEffects);
}
public static CharacterInfo ClientRead(string speciesName, IReadMessage inc)
{
ushort infoID = inc.ReadUInt16();
string newName = inc.ReadString();
int gender = inc.ReadByte();
int race = inc.ReadByte();
int headSpriteID = inc.ReadByte();
int hairIndex = inc.ReadByte();
int beardIndex = inc.ReadByte();
int moustacheIndex = inc.ReadByte();
int faceAttachmentIndex = inc.ReadByte();
string ragdollFile = inc.ReadString();
string jobIdentifier = inc.ReadString();
int variant = inc.ReadByte();
JobPrefab jobPrefab = null;
Dictionary<string, float> skillLevels = new Dictionary<string, float>();
if (!string.IsNullOrEmpty(jobIdentifier))
{
jobPrefab = JobPrefab.Get(jobIdentifier);
byte skillCount = inc.ReadByte();
for (int i = 0; i < skillCount; i++)
{
string skillIdentifier = inc.ReadString();
float skillLevel = inc.ReadSingle();
skillLevels.Add(skillIdentifier, skillLevel);
}
}
// TODO: animations
CharacterInfo ch = new CharacterInfo(speciesName, newName, jobPrefab, ragdollFile, variant)
{
ID = infoID,
};
ch.RecreateHead(headSpriteID,(Race)race, (Gender)gender, hairIndex, beardIndex, moustacheIndex, faceAttachmentIndex);
if (ch.Job != null)
{
foreach (KeyValuePair<string, float> skill in skillLevels)
{
Skill matchingSkill = ch.Job.Skills.Find(s => s.Identifier == skill.Key);
if (matchingSkill == null)
{
ch.Job.Skills.Add(new Skill(skill.Key, skill.Value));
continue;
}
matchingSkill.Level = skill.Value;
}
ch.Job.Skills.RemoveAll(s => !skillLevels.ContainsKey(s.Identifier));
}
return ch;
}
}
}

View File

@@ -0,0 +1,507 @@
using Barotrauma.Extensions;
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using System;
using System.Linq;
namespace Barotrauma
{
partial class Character
{
partial void UpdateNetInput()
{
if (GameMain.Client != null)
{
if (this != Controlled)
{
if (GameMain.Client.EndCinematic != null) // Freezes the characters during the ending cinematic
{
AnimController.Frozen = true;
memState.Clear();
return;
}
//freeze AI characters if more than x seconds have passed since last update from the server
if (lastRecvPositionUpdateTime < Lidgren.Network.NetTime.Now - NetConfig.FreezeCharacterIfPositionDataMissingDelay)
{
AnimController.Frozen = true;
memState.Clear();
//hide after y seconds
if (lastRecvPositionUpdateTime < Lidgren.Network.NetTime.Now - NetConfig.DisableCharacterIfPositionDataMissingDelay)
{
Enabled = false;
return;
}
}
}
else
{
var posInfo = new CharacterStateInfo(
SimPosition,
AnimController.Collider.Rotation,
LastNetworkUpdateID,
AnimController.TargetDir,
SelectedCharacter,
SelectedConstruction,
AnimController.Anim);
memLocalState.Add(posInfo);
InputNetFlags newInput = InputNetFlags.None;
if (IsKeyDown(InputType.Left)) newInput |= InputNetFlags.Left;
if (IsKeyDown(InputType.Right)) newInput |= InputNetFlags.Right;
if (IsKeyDown(InputType.Up)) newInput |= InputNetFlags.Up;
if (IsKeyDown(InputType.Down)) newInput |= InputNetFlags.Down;
if (IsKeyDown(InputType.Run)) newInput |= InputNetFlags.Run;
if (IsKeyDown(InputType.Crouch)) newInput |= InputNetFlags.Crouch;
if (IsKeyHit(InputType.Select)) newInput |= InputNetFlags.Select; //TODO: clean up the way this input is registered
if (IsKeyHit(InputType.Deselect)) newInput |= InputNetFlags.Deselect;
if (IsKeyHit(InputType.Health)) newInput |= InputNetFlags.Health;
if (IsKeyHit(InputType.Grab)) newInput |= InputNetFlags.Grab;
if (IsKeyDown(InputType.Use)) newInput |= InputNetFlags.Use;
if (IsKeyDown(InputType.Aim)) newInput |= InputNetFlags.Aim;
if (IsKeyDown(InputType.Shoot)) newInput |= InputNetFlags.Shoot;
if (IsKeyDown(InputType.Attack)) newInput |= InputNetFlags.Attack;
if (IsKeyDown(InputType.Ragdoll)) newInput |= InputNetFlags.Ragdoll;
if (AnimController.TargetDir == Direction.Left) newInput |= InputNetFlags.FacingLeft;
Vector2 relativeCursorPos = cursorPosition - AimRefPosition;
relativeCursorPos.Normalize();
UInt16 intAngle = (UInt16)(65535.0 * Math.Atan2(relativeCursorPos.Y, relativeCursorPos.X) / (2.0 * Math.PI));
NetInputMem newMem = new NetInputMem
{
states = newInput,
intAim = intAngle
};
if (focusedItem != null && !CharacterInventory.DraggingItemToWorld &&
(!newMem.states.HasFlag(InputNetFlags.Grab) && !newMem.states.HasFlag(InputNetFlags.Health)))
{
newMem.interact = focusedItem.ID;
}
else if (FocusedCharacter != null)
{
newMem.interact = FocusedCharacter.ID;
}
memInput.Insert(0, newMem);
LastNetworkUpdateID++;
if (memInput.Count > 60)
{
memInput.RemoveRange(60, memInput.Count - 60);
}
}
}
else //this == Character.Controlled && GameMain.Client == null
{
AnimController.Frozen = false;
}
}
public virtual void ClientWrite(IWriteMessage msg, object[] extraData = null)
{
if (extraData != null)
{
switch ((NetEntityEvent.Type)extraData[0])
{
case NetEntityEvent.Type.InventoryState:
msg.WriteRangedInteger(0, 0, 3);
Inventory.ClientWrite(msg, extraData);
break;
case NetEntityEvent.Type.Treatment:
msg.WriteRangedInteger(1, 0, 3);
msg.Write(AnimController.Anim == AnimController.Animation.CPR);
break;
case NetEntityEvent.Type.Status:
msg.WriteRangedInteger(2, 0, 3);
break;
}
}
else
{
msg.Write((byte)ClientNetObject.CHARACTER_INPUT);
if (memInput.Count > 60)
{
memInput.RemoveRange(60, memInput.Count - 60);
}
msg.Write(LastNetworkUpdateID);
byte inputCount = Math.Min((byte)memInput.Count, (byte)60);
msg.Write(inputCount);
for (int i = 0; i < inputCount; i++)
{
msg.WriteRangedInteger((int)memInput[i].states, 0, (int)InputNetFlags.MaxVal);
msg.Write(memInput[i].intAim);
if (memInput[i].states.HasFlag(InputNetFlags.Select) ||
memInput[i].states.HasFlag(InputNetFlags.Deselect) ||
memInput[i].states.HasFlag(InputNetFlags.Use) ||
memInput[i].states.HasFlag(InputNetFlags.Health) ||
memInput[i].states.HasFlag(InputNetFlags.Grab))
{
msg.Write(memInput[i].interact);
}
}
}
msg.WritePadBits();
}
public virtual void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
{
switch (type)
{
case ServerNetObject.ENTITY_POSITION:
bool facingRight = AnimController.Dir > 0.0f;
lastRecvPositionUpdateTime = (float)Lidgren.Network.NetTime.Now;
AnimController.Frozen = false;
Enabled = true;
UInt16 networkUpdateID = 0;
if (msg.ReadBoolean())
{
networkUpdateID = msg.ReadUInt16();
}
else
{
bool aimInput = msg.ReadBoolean();
keys[(int)InputType.Aim].Held = aimInput;
keys[(int)InputType.Aim].SetState(false, aimInput);
bool shootInput = msg.ReadBoolean();
keys[(int)InputType.Shoot].Held = shootInput;
keys[(int)InputType.Shoot].SetState(false, shootInput);
bool useInput = msg.ReadBoolean();
keys[(int)InputType.Use].Held = useInput;
keys[(int)InputType.Use].SetState(false, useInput);
if (AnimController is HumanoidAnimController)
{
bool crouching = msg.ReadBoolean();
keys[(int)InputType.Crouch].Held = crouching;
keys[(int)InputType.Crouch].SetState(false, crouching);
}
bool attackInput = msg.ReadBoolean();
keys[(int)InputType.Attack].Held = attackInput;
keys[(int)InputType.Attack].SetState(false, attackInput);
double aimAngle = msg.ReadUInt16() / 65535.0 * 2.0 * Math.PI;
cursorPosition = AimRefPosition + new Vector2((float)Math.Cos(aimAngle), (float)Math.Sin(aimAngle)) * 500.0f;
TransformCursorPos();
bool ragdollInput = msg.ReadBoolean();
keys[(int)InputType.Ragdoll].Held = ragdollInput;
keys[(int)InputType.Ragdoll].SetState(false, ragdollInput);
facingRight = msg.ReadBoolean();
}
bool entitySelected = msg.ReadBoolean();
Character selectedCharacter = null;
Item selectedItem = null;
AnimController.Animation animation = AnimController.Animation.None;
if (entitySelected)
{
ushort characterID = msg.ReadUInt16();
ushort itemID = msg.ReadUInt16();
selectedCharacter = FindEntityByID(characterID) as Character;
selectedItem = FindEntityByID(itemID) as Item;
if (characterID != NullEntityID)
{
bool doingCpr = msg.ReadBoolean();
if (doingCpr && SelectedCharacter != null)
{
animation = AnimController.Animation.CPR;
}
}
}
Vector2 pos = new Vector2(
msg.ReadSingle(),
msg.ReadSingle());
float MaxVel = NetConfig.MaxPhysicsBodyVelocity;
Vector2 linearVelocity = new Vector2(
msg.ReadRangedSingle(-MaxVel, MaxVel, 12),
msg.ReadRangedSingle(-MaxVel, MaxVel, 12));
linearVelocity = NetConfig.Quantize(linearVelocity, -MaxVel, MaxVel, 12);
bool fixedRotation = msg.ReadBoolean();
float? rotation = null;
float? angularVelocity = null;
if (!fixedRotation)
{
rotation = msg.ReadSingle();
float MaxAngularVel = NetConfig.MaxPhysicsBodyAngularVelocity;
angularVelocity = msg.ReadRangedSingle(-MaxAngularVel, MaxAngularVel, 8);
angularVelocity = NetConfig.Quantize(angularVelocity.Value, -MaxAngularVel, MaxAngularVel, 8);
}
bool readStatus = msg.ReadBoolean();
if (readStatus)
{
ReadStatus(msg);
}
msg.ReadPadBits();
int index = 0;
if (GameMain.Client.Character == this && CanMove)
{
var posInfo = new CharacterStateInfo(
pos, rotation,
networkUpdateID,
facingRight ? Direction.Right : Direction.Left,
selectedCharacter, selectedItem, animation);
while (index < memState.Count && NetIdUtils.IdMoreRecent(posInfo.ID, memState[index].ID))
index++;
memState.Insert(index, posInfo);
}
else
{
var posInfo = new CharacterStateInfo(
pos, rotation,
linearVelocity, angularVelocity,
sendingTime, facingRight ? Direction.Right : Direction.Left,
selectedCharacter, selectedItem, animation);
while (index < memState.Count && posInfo.Timestamp > memState[index].Timestamp)
index++;
memState.Insert(index, posInfo);
}
break;
case ServerNetObject.ENTITY_EVENT:
int eventType = msg.ReadRangedInteger(0, 3);
switch (eventType)
{
case 0:
if (Inventory == null)
{
string errorMsg = "Received an inventory update message for an entity with no inventory (" + Name + ", removed: " + Removed + ")";
DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce("CharacterNetworking.ClientRead:NoInventory" + ID, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
//read anyway to prevent messing up reading the rest of the message
UInt16 lastEventID = msg.ReadUInt16();
byte itemCount = msg.ReadByte();
for (int i = 0; i < itemCount; i++)
{
msg.ReadUInt16();
}
}
else
{
Inventory.ClientRead(type, msg, sendingTime);
}
break;
case 1:
byte ownerID = msg.ReadByte();
ResetNetState();
if (ownerID == GameMain.Client.ID)
{
if (controlled != null)
{
LastNetworkUpdateID = controlled.LastNetworkUpdateID;
}
if (!IsDead) { Controlled = this; }
IsRemotePlayer = false;
GameMain.Client.HasSpawned = true;
GameMain.Client.Character = this;
GameMain.LightManager.LosEnabled = true;
}
else
{
if (controlled == this)
{
Controlled = null;
IsRemotePlayer = ownerID > 0;
}
}
break;
case 2:
ReadStatus(msg);
break;
case 3:
int skillCount = msg.ReadByte();
for (int i = 0; i < skillCount; i++)
{
string skillIdentifier = msg.ReadString();
float skillLevel = msg.ReadSingle();
info?.SetSkillLevel(skillIdentifier, skillLevel, WorldPosition + Vector2.UnitY * 150.0f);
}
break;
}
msg.ReadPadBits();
break;
}
}
public static Character ReadSpawnData(IReadMessage inc)
{
DebugConsole.Log("Reading character spawn data");
if (GameMain.Client == null) return null;
bool noInfo = inc.ReadBoolean();
ushort id = inc.ReadUInt16();
string speciesName = inc.ReadString();
string seed = inc.ReadString();
Vector2 position = new Vector2(inc.ReadSingle(), inc.ReadSingle());
bool enabled = inc.ReadBoolean();
DebugConsole.Log("Received spawn data for " + speciesName);
Character character = null;
if (noInfo)
{
character = Create(speciesName, position, seed, null, true);
character.ID = id;
bool containsStatusData = inc.ReadBoolean();
if (containsStatusData)
{
character.ReadStatus(inc);
}
}
else
{
bool hasOwner = inc.ReadBoolean();
int ownerId = hasOwner ? inc.ReadByte() : -1;
byte teamID = inc.ReadByte();
bool hasAi = inc.ReadBoolean();
string infoSpeciesName = inc.ReadString();
CharacterInfo info = CharacterInfo.ClientRead(infoSpeciesName, inc);
character = Create(speciesName, position, seed, info, GameMain.Client.ID != ownerId, hasAi);
character.ID = id;
character.TeamID = (TeamType)teamID;
// Check if the character has a current order
if (inc.ReadBoolean())
{
int orderPrefabIndex = inc.ReadByte();
Entity targetEntity = FindEntityByID(inc.ReadUInt16());
Character orderGiver = inc.ReadBoolean() ? FindEntityByID(inc.ReadUInt16()) as Character : null;
int orderOptionIndex = inc.ReadByte();
if (orderPrefabIndex >= 0 && orderPrefabIndex < Order.PrefabList.Count)
{
var orderPrefab = Order.PrefabList[orderPrefabIndex];
if ((orderPrefab.ItemComponentType == null && orderPrefab.ItemIdentifiers.None()) ||
(targetEntity != null && (targetEntity as Item).Components.Any(c => c?.GetType() == orderPrefab.ItemComponentType)))
{
character.SetOrder(
new Order(orderPrefab, targetEntity, (targetEntity as Item)?.Components.FirstOrDefault(c => c?.GetType() == orderPrefab.ItemComponentType), orderGiver: orderGiver),
orderOptionIndex >= 0 && orderOptionIndex < orderPrefab.Options.Length ? orderPrefab.Options[orderOptionIndex] : null,
orderGiver, speak: false);
}
else
{
DebugConsole.ThrowError("Could not set order \"" + orderPrefab.Identifier + "\" for character \"" + character.Name + "\" because required target entity was not found.");
}
}
else
{
DebugConsole.ThrowError("Invalid order prefab index - index (" + orderPrefabIndex + ") out of bounds.");
}
}
bool containsStatusData = inc.ReadBoolean();
if (containsStatusData)
{
character.ReadStatus(inc);
}
if (character.IsHuman && character.TeamID != TeamType.FriendlyNPC && !character.IsDead)
{
CharacterInfo duplicateCharacterInfo = GameMain.GameSession.CrewManager.GetCharacterInfos().FirstOrDefault(c => c.ID == info.ID);
GameMain.GameSession.CrewManager.RemoveCharacterInfo(duplicateCharacterInfo);
GameMain.GameSession.CrewManager.AddCharacter(character);
}
if (GameMain.Client.ID == ownerId)
{
GameMain.Client.HasSpawned = true;
GameMain.Client.Character = character;
if (!character.IsDead) { Controlled = character; }
GameMain.LightManager.LosEnabled = true;
character.memInput.Clear();
character.memState.Clear();
character.memLocalState.Clear();
}
}
character.Enabled = Controlled == character || enabled;
return character;
}
private void ReadStatus(IReadMessage msg)
{
bool isDead = msg.ReadBoolean();
if (isDead)
{
CauseOfDeathType causeOfDeathType = (CauseOfDeathType)msg.ReadRangedInteger(0, Enum.GetValues(typeof(CauseOfDeathType)).Length - 1);
AfflictionPrefab causeOfDeathAffliction = null;
if (causeOfDeathType == CauseOfDeathType.Affliction)
{
string afflictionName = msg.ReadString();
if (!AfflictionPrefab.Prefabs.ContainsKey(afflictionName))
{
string errorMsg = $"Error in CharacterNetworking.ReadStatus: affliction not found ({afflictionName})";
causeOfDeathType = CauseOfDeathType.Unknown;
GameAnalyticsManager.AddErrorEventOnce("CharacterNetworking.ReadStatus:AfflictionIndexOutOfBounts", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
}
else
{
causeOfDeathAffliction = AfflictionPrefab.Prefabs[afflictionName];
}
}
byte severedLimbCount = msg.ReadByte();
if (!IsDead)
{
if (causeOfDeathType == CauseOfDeathType.Pressure)
{
Implode(true);
}
else
{
Kill(causeOfDeathType, causeOfDeathAffliction?.Instantiate(1.0f), true);
}
}
for (int i = 0; i < severedLimbCount; i++)
{
int severedJointIndex = msg.ReadByte();
if (severedJointIndex < 0 || severedJointIndex >= AnimController.LimbJoints.Length)
{
string errorMsg = $"Error in CharacterNetworking.ReadStatus: severed joint index out of bounds (index: {severedJointIndex}, joint count: {AnimController.LimbJoints.Length})";
GameAnalyticsManager.AddErrorEventOnce("CharacterNetworking.ReadStatus:JointIndexOutOfBounts", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
}
else
{
AnimController.SeverLimbJoint(AnimController.LimbJoints[severedJointIndex]);
}
}
}
else
{
if (IsDead) { Revive(); }
CharacterHealth.ClientRead(msg);
}
}
}
}

View File

@@ -14,8 +14,8 @@ namespace Barotrauma
public SoundType Type => Params.State;
public Gender Gender => Params.Gender;
public float Volume => roundSound.Volume;
public float Range => roundSound.Range;
public float Volume => roundSound == null ? 0.0f : roundSound.Volume;
public float Range => roundSound == null ? 0.0f : roundSound.Range;
public Sound Sound => roundSound?.Sound;
public CharacterSound(CharacterParams.SoundParams soundParams)

View File

@@ -41,7 +41,7 @@ namespace Barotrauma
private Submarine parentSub;
public HUDProgressBar(Vector2 worldPosition, Submarine parentSubmarine = null)
: this(worldPosition, parentSubmarine, Color.Red, Color.Green)
: this(worldPosition, parentSubmarine, GUI.Style.Red, GUI.Style.Green)
{
}

View File

@@ -0,0 +1,34 @@
using Microsoft.Xna.Framework;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma
{
partial class AfflictionHusk : Affliction
{
partial void UpdateMessages(float prevStrength, Character character)
{
if (Strength < Prefab.MaxStrength * 0.5f)
{
if (prevStrength % 10.0f > 0.05f && Strength % 10.0f < 0.05f)
{
GUI.AddMessage(TextManager.Get("HuskDormant"), GUI.Style.Red);
}
}
else if (Strength < Prefab.MaxStrength)
{
if (state == InfectionState.Dormant && Character.Controlled == character)
{
GUI.AddMessage(TextManager.Get("HuskCantSpeak"), GUI.Style.Red);
}
}
else if (state != InfectionState.Active && Character.Controlled == character)
{
GUI.AddMessage(TextManager.GetWithVariable("HuskActivate", "[Attack]", GameMain.Config.KeyBindText(InputType.Attack)),
GUI.Style.Red);
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,125 @@
using Microsoft.Xna.Framework;
using System.Linq;
using System;
using System.Xml.Linq;
using System.Collections.Generic;
namespace Barotrauma
{
partial class JobPrefab : IPrefab, IDisposable
{
public GUIButton CreateInfoFrame(int variant)
{
int width = 500, height = 400;
GUIButton backFrame = new GUIButton(new RectTransform(Vector2.One, GUI.Canvas), style: "GUIBackgroundBlocker");
GUIFrame frame = new GUIFrame(new RectTransform(new Point(width, height), backFrame.RectTransform, Anchor.Center));
GUIFrame paddedFrame = new GUIFrame(new RectTransform(new Vector2(0.9f, 0.9f), frame.RectTransform, Anchor.Center), style: null);
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.1f), paddedFrame.RectTransform), Name, font: GUI.LargeFont);
var descriptionBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedFrame.RectTransform) { RelativeOffset = new Vector2(0.0f, 0.15f) },
Description, font: GUI.SmallFont, wrap: true);
var skillContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.45f, 0.5f), paddedFrame.RectTransform)
{ RelativeOffset = new Vector2(0.0f, 0.2f + descriptionBlock.RectTransform.RelativeSize.Y) });
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), skillContainer.RectTransform),
TextManager.Get("Skills"), font: GUI.LargeFont);
foreach (SkillPrefab skill in Skills)
{
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), skillContainer.RectTransform),
" - " + TextManager.AddPunctuation(':', TextManager.Get("SkillName." + skill.Identifier), (int)skill.LevelRange.X + " - " + (int)skill.LevelRange.Y),
font: GUI.SmallFont);
}
/*if (!ItemIdentifiers.TryGetValue(variant, out var itemIdentifiers)) { return backFrame; }
var itemContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.45f, 0.5f), paddedFrame.RectTransform, Anchor.TopRight)
{ RelativeOffset = new Vector2(0.0f, 0.2f + descriptionBlock.RectTransform.RelativeSize.Y) })
{
Stretch = true
};
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), itemContainer.RectTransform),
TextManager.Get("Items", fallBackTag: "mapentitycategory.equipment"), font: GUI.LargeFont);
foreach (string identifier in itemIdentifiers.Distinct())
{
if (!(MapEntityPrefab.Find(name: null, identifier: identifier) is ItemPrefab itemPrefab)) { continue; }
int count = itemIdentifiers.Count(i => i == identifier);
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), itemContainer.RectTransform),
" - " + (count == 1 ? itemPrefab.Name : itemPrefab.Name + " x" + count),
font: GUI.SmallFont);
}*/
return backFrame;
}
public class OutfitPreview
{
/// <summary>
/// Pair.First = sprite, Pair.Second = draw offset
/// </summary>
public readonly List<Pair<Sprite, Vector2>> Sprites;
public Vector2 Dimensions;
public OutfitPreview()
{
Sprites = new List<Pair<Sprite, Vector2>>();
Dimensions = Vector2.One;
}
public void AddSprite(Sprite sprite, Vector2 drawOffset)
{
Sprites.Add(new Pair<Sprite, Vector2>(sprite, drawOffset));
}
}
public List<OutfitPreview> GetJobOutfitSprites(Gender gender, bool useInventoryIcon, out Vector2 maxDimensions)
{
List<OutfitPreview> outfitPreviews = new List<OutfitPreview>();
maxDimensions = Vector2.One;
var equipIdentifiers = Element.GetChildElements("ItemSet").Elements().Where(e => e.GetAttributeBool("outfit", false)).Select(e => e.GetAttributeString("identifier", ""));
var outfitPrefabs = ItemPrefab.Prefabs.Where(itemPrefab => equipIdentifiers.Contains(itemPrefab.Identifier)).ToList();
if (!outfitPrefabs.Any()) { return null; }
for (int i = 0; i < outfitPrefabs.Count; i++)
{
var outfitPreview = new OutfitPreview();
if (!ItemSets.TryGetValue(i, out var itemSetElement)) { continue; }
var previewElement = itemSetElement.GetChildElement("PreviewSprites");
if (previewElement == null || useInventoryIcon)
{
if (outfitPrefabs[i] is ItemPrefab prefab && prefab.InventoryIcon != null)
{
outfitPreview.AddSprite(prefab.InventoryIcon, Vector2.Zero);
outfitPreview.Dimensions = prefab.InventoryIcon.SourceRect.Size.ToVector2();
maxDimensions.X = MathHelper.Max(maxDimensions.X, outfitPreview.Dimensions.X);
maxDimensions.Y = MathHelper.Max(maxDimensions.Y, outfitPreview.Dimensions.Y);
}
outfitPreviews.Add(outfitPreview);
continue;
}
var children = previewElement.Elements().ToList();
for (int n = 0; n < children.Count; n++)
{
XElement spriteElement = children[n];
string spriteTexture = spriteElement.GetAttributeString("texture", "").Replace("[GENDER]", (gender == Gender.Female) ? "female" : "male");
var sprite = new Sprite(spriteElement, file: spriteTexture);
sprite.size = new Vector2(sprite.SourceRect.Width, sprite.SourceRect.Height);
outfitPreview.AddSprite(sprite, children[n].GetAttributeVector2("offset", Vector2.Zero));
}
outfitPreview.Dimensions = previewElement.GetAttributeVector2("dims", Vector2.One);
maxDimensions.X = MathHelper.Max(maxDimensions.X, outfitPreview.Dimensions.X);
maxDimensions.Y = MathHelper.Max(maxDimensions.Y, outfitPreview.Dimensions.Y);
outfitPreviews.Add(outfitPreview);
}
return outfitPreviews;
}
}
}

View File

@@ -0,0 +1,893 @@
using Barotrauma.Lights;
using Barotrauma.Particles;
using Barotrauma.SpriteDeformations;
using Barotrauma.Extensions;
using FarseerPhysics;
using FarseerPhysics.Dynamics.Joints;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml.Linq;
using SpriteParams = Barotrauma.RagdollParams.SpriteParams;
namespace Barotrauma
{
partial class LimbJoint : RevoluteJoint
{
public void UpdateDeformations(float deltaTime)
{
float diff = Math.Abs(UpperLimit - LowerLimit);
float strength = MathHelper.Lerp(0, 1, MathUtils.InverseLerp(0, MathHelper.Pi, diff));
float jointAngle = this.JointAngle * strength;
JointBendDeformation limbADeformation = LimbA.Deformations.Find(d => d is JointBendDeformation) as JointBendDeformation;
JointBendDeformation limbBDeformation = LimbB.Deformations.Find(d => d is JointBendDeformation) as JointBendDeformation;
if (limbADeformation != null && limbBDeformation != null)
{
UpdateBend(LimbA, limbADeformation, this.LocalAnchorA, -jointAngle);
UpdateBend(LimbB, limbBDeformation, this.LocalAnchorB, jointAngle);
}
void UpdateBend(Limb limb, JointBendDeformation deformation, Vector2 localAnchor, float angle)
{
deformation.Scale = limb.DeformSprite.Size;
Vector2 displayAnchor = ConvertUnits.ToDisplayUnits(localAnchor);
displayAnchor.Y = -displayAnchor.Y;
Vector2 refPos = displayAnchor + limb.DeformSprite.Origin;
refPos.X /= limb.DeformSprite.Size.X;
refPos.Y /= limb.DeformSprite.Size.Y;
if (Math.Abs(localAnchor.X) > Math.Abs(localAnchor.Y))
{
if (localAnchor.X > 0.0f)
{
deformation.BendRightRefPos = refPos;
deformation.BendRight = angle;
}
else
{
deformation.BendLeftRefPos = refPos;
deformation.BendLeft = angle;
}
}
else
{
if (localAnchor.Y > 0.0f)
{
deformation.BendUpRefPos = refPos;
deformation.BendUp = angle;
}
else
{
deformation.BendDownRefPos = refPos;
deformation.BendDown = angle;
}
}
}
}
public void Draw(SpriteBatch spriteBatch)
{
// TODO: move this into the character editor
//var mouthPos = ragdoll.GetMouthPosition();
//if (mouthPos != null)
//{
// var pos = ConvertUnits.ToDisplayUnits(mouthPos.Value);
// pos.Y = -pos.Y;
// ShapeExtensions.DrawPoint(spriteBatch, pos, GUI.Style.Red, size: 5);
//}
return;
// A debug visualisation on the bezier curve between limbs.
var start = LimbA.WorldPosition;
var end = LimbB.WorldPosition;
var jointAPos = ConvertUnits.ToDisplayUnits(LocalAnchorA);
var control = start + Vector2.Transform(jointAPos, Matrix.CreateRotationZ(LimbA.Rotation));
start.Y = -start.Y;
end.Y = -end.Y;
control.Y = -control.Y;
//GUI.DrawRectangle(spriteBatch, start, Vector2.One * 5, Color.White, true);
//GUI.DrawRectangle(spriteBatch, end, Vector2.One * 5, Color.Black, true);
//GUI.DrawRectangle(spriteBatch, control, Vector2.One * 5, Color.Black, true);
//GUI.DrawLine(spriteBatch, start, end, Color.White);
//GUI.DrawLine(spriteBatch, start, control, Color.Black);
//GUI.DrawLine(spriteBatch, control, end, Color.Black);
GUI.DrawBezierWithDots(spriteBatch, start, end, control, 1000, GUI.Style.Red);
}
}
partial class Limb
{
//minimum duration between hit/attack sounds
public const float SoundInterval = 0.4f;
public float LastAttackSoundTime, LastImpactSoundTime;
private float wetTimer;
private float dripParticleTimer;
/// <summary>
/// Note that different limbs can share the same deformations.
/// Use ragdoll.SpriteDeformations for a collection that cannot have duplicates.
/// </summary>
public List<SpriteDeformation> Deformations { get; private set; } = new List<SpriteDeformation>();
public Sprite Sprite { get; protected set; }
protected DeformableSprite _deformSprite;
public DeformableSprite DeformSprite
{
get
{
var conditionalSprite = ConditionalSprites.FirstOrDefault(c => c.IsActive && c.DeformableSprite != null);
if (conditionalSprite != null)
{
return conditionalSprite.DeformableSprite;
}
else
{
return _deformSprite;
}
}
}
public List<DecorativeSprite> DecorativeSprites { get; private set; } = new List<DecorativeSprite>();
public Sprite ActiveSprite
{
get
{
var conditionalSprite = ConditionalSprites.FirstOrDefault(c => c.IsActive && c.ActiveSprite != null);
if (conditionalSprite != null)
{
return conditionalSprite.ActiveSprite;
}
else
{
return _deformSprite != null ? _deformSprite.Sprite : Sprite;
}
}
}
public WearableSprite HuskSprite { get; private set; }
public WearableSprite HerpesSprite { get; private set; }
public void LoadHuskSprite() => HuskSprite = GetWearableSprite(WearableType.Husk);
public void LoadHerpesSprite() => HerpesSprite = GetWearableSprite(WearableType.Herpes);
public float TextureScale => Params.Ragdoll.TextureScale;
public Sprite DamagedSprite { get; private set; }
public List<ConditionalSprite> ConditionalSprites { get; private set; } = new List<ConditionalSprite>();
private Dictionary<DecorativeSprite, SpriteState> spriteAnimState = new Dictionary<DecorativeSprite, SpriteState>();
private Dictionary<int, List<DecorativeSprite>> DecorativeSpriteGroups = new Dictionary<int, List<DecorativeSprite>>();
class SpriteState
{
public float RotationState;
public float OffsetState;
public bool IsActive = true;
}
public Color InitialLightSourceColor
{
get;
private set;
}
public float? InitialLightSpriteAlpha
{
get;
private set;
}
public LightSource LightSource
{
get;
private set;
}
private float damageOverlayStrength;
public float DamageOverlayStrength
{
get { return damageOverlayStrength; }
set { damageOverlayStrength = MathHelper.Clamp(value, 0.0f, 100.0f); }
}
private float burnOverLayStrength;
public float BurnOverlayStrength
{
get { return burnOverLayStrength; }
set { burnOverLayStrength = MathHelper.Clamp(value, 0.0f, 100.0f); }
}
public string HitSoundTag => Params?.Sound?.Tag;
private List<WearableSprite> wearableTypeHidingSprites = new List<WearableSprite>();
private List<WearableType> wearableTypesToHide = new List<WearableType>();
private bool enableHuskSprite;
public bool EnableHuskSprite
{
get
{
return enableHuskSprite;
}
set
{
if (HuskSprite != null && value != enableHuskSprite)
{
if (value)
{
List<WearableSprite> otherWearablesWithHusk = new List<WearableSprite>() { HuskSprite };
otherWearablesWithHusk.AddRange(OtherWearables);
OtherWearables = otherWearablesWithHusk;
UpdateWearableTypesToHide();
}
else
{
OtherWearables.Remove(HuskSprite);
UpdateWearableTypesToHide();
}
}
enableHuskSprite = value;
}
}
partial void InitProjSpecific(XElement element)
{
for (int i = 0; i < Params.decorativeSpriteParams.Count; i++)
{
var param = Params.decorativeSpriteParams[i];
var decorativeSprite = new DecorativeSprite(param.Element, file: GetSpritePath(param.Element, param));
DecorativeSprites.Add(decorativeSprite);
int groupID = decorativeSprite.RandomGroupID;
if (!DecorativeSpriteGroups.ContainsKey(groupID))
{
DecorativeSpriteGroups.Add(groupID, new List<DecorativeSprite>());
}
DecorativeSpriteGroups[groupID].Add(decorativeSprite);
spriteAnimState.Add(decorativeSprite, new SpriteState());
}
foreach (XElement subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "sprite":
Sprite = new Sprite(subElement, file: GetSpritePath(subElement, Params.normalSpriteParams));
break;
case "damagedsprite":
DamagedSprite = new Sprite(subElement, file: GetSpritePath(subElement, Params.damagedSpriteParams));
break;
case "conditionalsprite":
var conditionalSprite = new ConditionalSprite(subElement, character, file: GetSpritePath(subElement, null));
ConditionalSprites.Add(conditionalSprite);
if (conditionalSprite.DeformableSprite != null)
{
CreateDeformations(subElement.GetChildElement("deformablesprite"));
}
break;
case "deformablesprite":
_deformSprite = new DeformableSprite(subElement, filePath: GetSpritePath(subElement, Params.deformSpriteParams));
CreateDeformations(subElement);
break;
case "lightsource":
LightSource = new LightSource(subElement)
{
ParentBody = body,
SpriteScale = Vector2.One * Scale * TextureScale
};
InitialLightSourceColor = LightSource.Color;
InitialLightSpriteAlpha = LightSource.OverrideLightSpriteAlpha;
break;
}
void CreateDeformations(XElement e)
{
foreach (XElement animationElement in e.GetChildElements("spritedeformation"))
{
int sync = animationElement.GetAttributeInt("sync", -1);
SpriteDeformation deformation = null;
if (sync > -1)
{
// if the element is marked with the sync attribute, use a deformation of the same type with the same sync value, if there is one already.
string typeName = animationElement.GetAttributeString("type", "").ToLowerInvariant();
deformation = ragdoll.Limbs
.Where(l => l != null)
.SelectMany(l => l.Deformations)
.Where(d => d.TypeName == typeName && d.Sync == sync)
.FirstOrDefault();
}
if (deformation == null)
{
deformation = SpriteDeformation.Load(animationElement, character.SpeciesName);
if (deformation != null)
{
ragdoll.SpriteDeformations.Add(deformation);
}
}
if (deformation != null)
{
Deformations.Add(deformation);
}
}
}
}
}
public void RecreateSprites()
{
if (Sprite != null)
{
Sprite.Remove();
var source = Sprite.SourceElement;
Sprite = new Sprite(source, file: GetSpritePath(source, Params.normalSpriteParams));
}
if (_deformSprite != null)
{
_deformSprite.Remove();
var source = _deformSprite.Sprite.SourceElement;
_deformSprite = new DeformableSprite(source, filePath: GetSpritePath(source, Params.deformSpriteParams));
}
if (DamagedSprite != null)
{
DamagedSprite.Remove();
var source = DamagedSprite.SourceElement;
DamagedSprite = new Sprite(source, file: GetSpritePath(source, Params.damagedSpriteParams));
}
for (int i = 0; i < ConditionalSprites.Count; i++)
{
var conditionalSprite = ConditionalSprites[i];
var source = conditionalSprite.ActiveSprite.SourceElement;
conditionalSprite.Remove();
ConditionalSprites[i] = new ConditionalSprite(source, character, file: GetSpritePath(source, null));
}
for (int i = 0; i < DecorativeSprites.Count; i++)
{
var decorativeSprite = DecorativeSprites[i];
decorativeSprite.Remove();
var source = decorativeSprite.Sprite.SourceElement;
DecorativeSprites[i] = new DecorativeSprite(source, file: GetSpritePath(source, Params.decorativeSpriteParams[i]));
}
}
private void CalculateHeadPosition(Sprite sprite)
{
if (type != LimbType.Head) { return; }
character.Info?.CalculateHeadPosition(sprite);
}
private string GetSpritePath(XElement element, SpriteParams spriteParams)
{
string texturePath = element.GetAttributeString("texture", null);
if (string.IsNullOrWhiteSpace(texturePath) && spriteParams != null)
{
texturePath = spriteParams.Ragdoll.Texture;
}
return GetSpritePath(texturePath);
}
/// <summary>
/// Get the full path of a limb sprite, taking into account tags, gender and head id
/// </summary>
private string GetSpritePath(string texturePath)
{
string spritePath = texturePath;
string spritePathWithTags = spritePath;
if (character.Info != null && character.IsHumanoid)
{
spritePath = spritePath.Replace("[GENDER]", (character.Info.Gender == Gender.Female) ? "female" : "male");
spritePath = spritePath.Replace("[RACE]", character.Info.Race.ToString().ToLowerInvariant());
spritePath = spritePath.Replace("[HEADID]", character.Info.HeadSpriteId.ToString());
if (character.Info.HeadSprite != null && character.Info.SpriteTags.Any())
{
string tags = "";
character.Info.SpriteTags.ForEach(tag => tags += "[" + tag + "]");
spritePathWithTags = Path.Combine(
Path.GetDirectoryName(spritePath),
Path.GetFileNameWithoutExtension(spritePath) + tags + Path.GetExtension(spritePath));
}
}
return File.Exists(spritePathWithTags) ? spritePathWithTags : spritePath;
}
partial void LoadParamsProjSpecific()
{
bool isFlipped = dir == Direction.Left;
Sprite?.LoadParams(Params.normalSpriteParams, isFlipped);
DamagedSprite?.LoadParams(Params.damagedSpriteParams, isFlipped);
_deformSprite?.Sprite.LoadParams(Params.deformSpriteParams, isFlipped);
for (int i = 0; i < DecorativeSprites.Count; i++)
{
DecorativeSprites[i].Sprite?.LoadParams(Params.decorativeSpriteParams[i], isFlipped);
}
}
partial void AddDamageProjSpecific(Vector2 simPosition, List<Affliction> afflictions, bool playSound, List<DamageModifier> appliedDamageModifiers)
{
float bleedingDamage = character.CharacterHealth.DoesBleed ? afflictions.FindAll(a => a is AfflictionBleeding).Sum(a => a.GetVitalityDecrease(character.CharacterHealth)) : 0;
float damage = afflictions.FindAll(a => a.Prefab.AfflictionType == "damage").Sum(a => a.GetVitalityDecrease(character.CharacterHealth));
float damageMultiplier = 1;
foreach (DamageModifier damageModifier in appliedDamageModifiers)
{
damageMultiplier *= damageModifier.DamageMultiplier;
}
if (playSound)
{
string damageSoundType = (bleedingDamage > damage) ? "LimbSlash" : "LimbBlunt";
foreach (DamageModifier damageModifier in appliedDamageModifiers)
{
if (!string.IsNullOrWhiteSpace(damageModifier.DamageSound))
{
damageSoundType = damageModifier.DamageSound;
break;
}
}
SoundPlayer.PlayDamageSound(damageSoundType, Math.Max(damage, bleedingDamage), WorldPosition);
}
// spawn damage particles
float damageParticleAmount = Math.Min(damage / 10, 1.0f) * damageMultiplier;
if (damageParticleAmount > 0.001f)
{
foreach (ParticleEmitter emitter in character.DamageEmitters)
{
if (inWater && emitter.Prefab.ParticlePrefab.DrawTarget == ParticlePrefab.DrawTargetType.Air) continue;
if (!inWater && emitter.Prefab.ParticlePrefab.DrawTarget == ParticlePrefab.DrawTargetType.Water) continue;
emitter.Emit(1.0f, WorldPosition, character.CurrentHull, amountMultiplier: damageParticleAmount);
}
}
if (bleedingDamage > 0)
{
float bloodParticleAmount = Math.Min(bleedingDamage / 5, 1.0f) * damageMultiplier;
float bloodParticleSize = MathHelper.Clamp(bleedingDamage / 5, 0.1f, 1.0f);
foreach (ParticleEmitter emitter in character.BloodEmitters)
{
if (inWater && emitter.Prefab.ParticlePrefab.DrawTarget == ParticlePrefab.DrawTargetType.Air) continue;
if (!inWater && emitter.Prefab.ParticlePrefab.DrawTarget == ParticlePrefab.DrawTargetType.Water) continue;
emitter.Emit(1.0f, WorldPosition, character.CurrentHull, sizeMultiplier: bloodParticleSize, amountMultiplier: bloodParticleAmount);
}
if (bloodParticleAmount > 0 && character.CurrentHull != null && !string.IsNullOrEmpty(character.BloodDecalName))
{
character.CurrentHull.AddDecal(character.BloodDecalName, WorldPosition, MathHelper.Clamp(bloodParticleSize, 0.5f, 1.0f));
}
}
}
partial void UpdateProjSpecific(float deltaTime)
{
if (!body.Enabled) return;
if (!character.IsDead)
{
DamageOverlayStrength -= deltaTime;
BurnOverlayStrength -= deltaTime;
}
if (inWater)
{
wetTimer = 1.0f;
}
else
{
wetTimer -= deltaTime * 0.1f;
if (wetTimer > 0.0f)
{
dripParticleTimer += wetTimer * deltaTime * Mass * (wetTimer > 0.9f ? 50.0f : 5.0f);
if (dripParticleTimer > 1.0f)
{
float dropRadius = body.BodyShape == PhysicsBody.Shape.Rectangle ? Math.Min(body.width, body.height) : body.radius;
GameMain.ParticleManager.CreateParticle(
"waterdrop",
WorldPosition + Rand.Vector(Rand.Range(0.0f, ConvertUnits.ToDisplayUnits(dropRadius))),
ConvertUnits.ToDisplayUnits(body.LinearVelocity),
0, character.CurrentHull);
dripParticleTimer = 0.0f;
}
}
}
if (LightSource != null)
{
LightSource.ParentSub = body.Submarine;
LightSource.Rotation = (dir == Direction.Right) ? body.Rotation : body.Rotation - MathHelper.Pi;
if (LightSource.LightSprite != null)
{
LightSource.LightSprite.Depth = ActiveSprite.Depth;
}
}
UpdateSpriteStates(deltaTime);
}
public void Draw(SpriteBatch spriteBatch, Camera cam, Color? overrideColor = null)
{
float brightness = 1.0f - (burnOverLayStrength / 100.0f) * 0.5f;
Color color = new Color(brightness, brightness, brightness);
color = overrideColor ?? color;
if (isSevered)
{
if (severedFadeOutTimer > SeveredFadeOutTime)
{
if (LightSource != null) { LightSource.Enabled = false; }
return;
}
else if (severedFadeOutTimer > SeveredFadeOutTime - 1.0f)
{
color *= SeveredFadeOutTime - severedFadeOutTimer;
}
}
body.Dir = Dir;
float herpesStrength = character.CharacterHealth.GetAfflictionStrength("spaceherpes");
bool hideLimb = Params.Hide ||
OtherWearables.Any(w => w.HideLimb) ||
wearingItems.Any(w => w != null && w.HideLimb);
var activeSprite = ActiveSprite;
if (type == LimbType.Head)
{
CalculateHeadPosition(activeSprite);
}
body.UpdateDrawPosition();
if (!hideLimb)
{
var deformSprite = DeformSprite;
if (deformSprite != null)
{
if (Deformations != null && Deformations.Any())
{
var deformation = SpriteDeformation.GetDeformation(Deformations, deformSprite.Size);
deformSprite.Deform(deformation);
}
else
{
deformSprite.Reset();
}
body.Draw(deformSprite, cam, Vector2.One * Scale * TextureScale, color, Params.MirrorHorizontally);
}
else
{
body.Draw(spriteBatch, activeSprite, color, null, Scale * TextureScale, Params.MirrorHorizontally, Params.MirrorVertically);
}
}
SpriteEffects spriteEffect = (dir == Direction.Right) ? SpriteEffects.None : SpriteEffects.FlipHorizontally;
if (LightSource != null)
{
LightSource.LightSpriteEffect = (dir == Direction.Right) ? SpriteEffects.None : SpriteEffects.FlipVertically;
}
if (damageOverlayStrength > 0.0f && DamagedSprite != null && !hideLimb)
{
DamagedSprite.Draw(spriteBatch,
new Vector2(body.DrawPosition.X, -body.DrawPosition.Y),
color * Math.Min(damageOverlayStrength, 1.0f), activeSprite.Origin,
-body.DrawRotation,
Scale, spriteEffect, activeSprite.Depth - 0.0000015f);
}
foreach (var decorativeSprite in DecorativeSprites)
{
if (!spriteAnimState[decorativeSprite].IsActive) { continue; }
float rotation = decorativeSprite.GetRotation(ref spriteAnimState[decorativeSprite].RotationState);
Vector2 offset = decorativeSprite.GetOffset(ref spriteAnimState[decorativeSprite].OffsetState) * Scale;
var ca = (float)Math.Cos(-body.Rotation);
var sa = (float)Math.Sin(-body.Rotation);
Vector2 transformedOffset = new Vector2(ca * offset.X + sa * offset.Y, -sa * offset.X + ca * offset.Y);
decorativeSprite.Sprite.Draw(spriteBatch, new Vector2(body.DrawPosition.X + transformedOffset.X, -(body.DrawPosition.Y + transformedOffset.Y)), color,
-body.Rotation + rotation, Scale, spriteEffect,
depth: decorativeSprite.Sprite.Depth);
}
float depthStep = 0.000001f;
float step = depthStep;
WearableSprite onlyDrawable = wearingItems.Find(w => w.HideOtherWearables);
if (Params.MirrorHorizontally)
{
spriteEffect = spriteEffect == SpriteEffects.None ? SpriteEffects.FlipHorizontally : SpriteEffects.None;
}
if (Params.MirrorVertically)
{
spriteEffect |= SpriteEffects.FlipVertically;
}
if (onlyDrawable == null)
{
if (HerpesSprite != null && !wearableTypesToHide.Contains(WearableType.Herpes))
{
DrawWearable(HerpesSprite, depthStep, spriteBatch, color * Math.Min(herpesStrength / 10.0f, 1.0f), spriteEffect);
depthStep += step;
}
foreach (WearableSprite wearable in OtherWearables)
{
if (wearableTypesToHide.Contains(wearable.Type)) { continue; }
DrawWearable(wearable, depthStep, spriteBatch, color, spriteEffect);
//if there are multiple sprites on this limb, make the successive ones be drawn in front
depthStep += step;
}
}
foreach (WearableSprite wearable in WearingItems)
{
if (onlyDrawable != null && onlyDrawable != wearable) continue;
DrawWearable(wearable, depthStep, spriteBatch, color, spriteEffect);
//if there are multiple sprites on this limb, make the successive ones be drawn in front
depthStep += step;
}
if (GameMain.DebugDraw)
{
if (pullJoint != null)
{
Vector2 pos = ConvertUnits.ToDisplayUnits(pullJoint.WorldAnchorB);
GUI.DrawRectangle(spriteBatch, new Rectangle((int)pos.X, (int)-pos.Y, 5, 5), GUI.Style.Red, true);
}
var bodyDrawPos = body.DrawPosition;
bodyDrawPos.Y = -bodyDrawPos.Y;
if (IsStuck)
{
Vector2 from = ConvertUnits.ToDisplayUnits(attachJoint.WorldAnchorA);
from.Y = -from.Y;
Vector2 to = ConvertUnits.ToDisplayUnits(attachJoint.WorldAnchorB);
to.Y = -to.Y;
var localFront = body.GetLocalFront(Params.GetSpriteOrientation());
var front = ConvertUnits.ToDisplayUnits(body.FarseerBody.GetWorldPoint(localFront));
front.Y = -front.Y;
GUI.DrawLine(spriteBatch, bodyDrawPos, front, Color.Yellow, width: 2);
GUI.DrawLine(spriteBatch, from, to, GUI.Style.Red, width: 1);
GUI.DrawRectangle(spriteBatch, new Rectangle((int)from.X, (int)from.Y, 12, 12), Color.White, true);
GUI.DrawRectangle(spriteBatch, new Rectangle((int)to.X, (int)to.Y, 12, 12), Color.White, true);
GUI.DrawRectangle(spriteBatch, new Rectangle((int)from.X, (int)from.Y, 10, 10), Color.Blue, true);
GUI.DrawRectangle(spriteBatch, new Rectangle((int)to.X, (int)to.Y, 10, 10), GUI.Style.Red, true);
GUI.DrawRectangle(spriteBatch, new Rectangle((int)front.X, (int)front.Y, 10, 10), Color.Yellow, true);
//Vector2 mainLimbFront = ConvertUnits.ToDisplayUnits(ragdoll.MainLimb.body.FarseerBody.GetWorldPoint(ragdoll.MainLimb.body.GetFrontLocal(MathHelper.ToRadians(limbParams.Orientation))));
//mainLimbFront.Y = -mainLimbFront.Y;
//var mainLimbDrawPos = ragdoll.MainLimb.body.DrawPosition;
//mainLimbDrawPos.Y = -mainLimbDrawPos.Y;
//GUI.DrawLine(spriteBatch, mainLimbDrawPos, mainLimbFront, Color.White, width: 5);
//GUI.DrawRectangle(spriteBatch, new Rectangle((int)mainLimbFront.X, (int)mainLimbFront.Y, 10, 10), Color.Yellow, true);
}
//DrawDamageModifiers(spriteBatch, cam, bodyDrawPos, isScreenSpace: false);
}
}
public void UpdateWearableTypesToHide()
{
wearableTypeHidingSprites.Clear();
if (WearingItems != null && WearingItems.Count > 0)
{
wearableTypeHidingSprites.AddRange(
WearingItems.FindAll(w => w.HideWearablesOfType != null && w.HideWearablesOfType.Count > 0));
}
if (OtherWearables != null && OtherWearables.Count > 0)
{
wearableTypeHidingSprites.AddRange(
OtherWearables.FindAll(w => w.HideWearablesOfType != null && w.HideWearablesOfType.Count > 0));
}
wearableTypesToHide.Clear();
if (wearableTypeHidingSprites.Count > 0)
{
foreach (WearableSprite sprite in wearableTypeHidingSprites)
{
foreach (WearableType type in sprite.HideWearablesOfType)
{
if (!wearableTypesToHide.Contains(type))
{
wearableTypesToHide.Add(type);
}
}
}
}
}
private void UpdateSpriteStates(float deltaTime)
{
foreach (int spriteGroup in DecorativeSpriteGroups.Keys)
{
for (int i = 0; i < DecorativeSpriteGroups[spriteGroup].Count; i++)
{
var decorativeSprite = DecorativeSpriteGroups[spriteGroup][i];
if (decorativeSprite == null) { continue; }
if (spriteGroup > 0)
{
// TODO
//int activeSpriteIndex = ID % DecorativeSpriteGroups[spriteGroup].Count;
//if (i != activeSpriteIndex)
//{
// spriteAnimState[decorativeSprite].IsActive = false;
// continue;
//}
}
//check if the sprite is active (whether it should be drawn or not)
var spriteState = spriteAnimState[decorativeSprite];
spriteState.IsActive = true;
foreach (PropertyConditional conditional in decorativeSprite.IsActiveConditionals)
{
if (!conditional.Matches(this))
{
spriteState.IsActive = false;
break;
}
}
if (!spriteState.IsActive) { continue; }
//check if the sprite should be animated
bool animate = true;
foreach (PropertyConditional conditional in decorativeSprite.AnimationConditionals)
{
if (!conditional.Matches(this)) { animate = false; break; }
}
if (!animate) { continue; }
spriteState.OffsetState += deltaTime;
spriteState.RotationState += deltaTime;
}
}
}
public void DrawDamageModifiers(SpriteBatch spriteBatch, Camera cam, Vector2 startPos, bool isScreenSpace)
{
foreach (var modifier in DamageModifiers)
{
//Vector2 up = VectorExtensions.Backward(-body.TransformedRotation + Params.GetSpriteOrientation() * Dir);
//int width = 4;
//if (!isScreenSpace)
//{
// width = (int)Math.Round(width / cam.Zoom);
//}
//GUI.DrawLine(spriteBatch, startPos, startPos + Vector2.Normalize(up) * size, GUI.Style.Red, width: width);
Color color = modifier.DamageMultiplier > 1 ? GUI.Style.Red : GUI.Style.Green;
float size = ConvertUnits.ToDisplayUnits(body.GetSize().Length() / 2);
if (isScreenSpace)
{
size *= cam.Zoom;
}
int thickness = 2;
if (!isScreenSpace)
{
thickness = (int)Math.Round(thickness / cam.Zoom);
}
float bodyRotation = -body.Rotation;
float constantOffset = -MathHelper.PiOver2;
Vector2 armorSector = modifier.ArmorSectorInRadians;
float armorSectorSize = Math.Abs(armorSector.X - armorSector.Y);
float radians = armorSectorSize * Dir;
float armorSectorOffset = armorSector.X * Dir;
float finalOffset = bodyRotation + constantOffset + armorSectorOffset;
ShapeExtensions.DrawSector(spriteBatch, startPos, size, radians, 40, color, finalOffset, thickness);
}
}
private void DrawWearable(WearableSprite wearable, float depthStep, SpriteBatch spriteBatch, Color color, SpriteEffects spriteEffect)
{
var sprite = ActiveSprite;
if (wearable.InheritSourceRect)
{
if (wearable.SheetIndex.HasValue)
{
wearable.Sprite.SourceRect = new Rectangle(CharacterInfo.CalculateOffset(sprite, wearable.SheetIndex.Value), sprite.SourceRect.Size);
}
else if (type == LimbType.Head && character.Info != null && character.Info.Head.SheetIndex.HasValue)
{
wearable.Sprite.SourceRect = new Rectangle(CharacterInfo.CalculateOffset(sprite, character.Info.Head.SheetIndex.Value.ToPoint()), sprite.SourceRect.Size);
}
else
{
wearable.Sprite.SourceRect = sprite.SourceRect;
}
}
Vector2 origin = wearable.Sprite.Origin;
if (wearable.InheritOrigin)
{
origin = sprite.Origin;
wearable.Sprite.Origin = origin;
}
else
{
origin = wearable.Sprite.Origin;
// If the wearable inherits the origin, flipping is already handled.
if (body.Dir == -1.0f)
{
origin.X = wearable.Sprite.SourceRect.Width - origin.X;
}
}
float depth = wearable.Sprite.Depth;
if (wearable.InheritLimbDepth)
{
depth = sprite.Depth - depthStep;
Limb depthLimb = (wearable.DepthLimb == LimbType.None) ? this : character.AnimController.GetLimb(wearable.DepthLimb);
if (depthLimb != null)
{
depth = depthLimb.ActiveSprite.Depth - depthStep;
}
}
var wearableItemComponent = wearable.WearableComponent;
Color wearableColor = Color.White;
if (wearableItemComponent != null)
{
// Draw outer cloths on top of inner cloths.
if (wearableItemComponent.AllowedSlots.Contains(InvSlotType.OuterClothes))
{
depth -= depthStep;
}
wearableColor = wearableItemComponent.Item.GetSpriteColor();
}
float textureScale = wearable.InheritTextureScale ? TextureScale : 1;
wearable.Sprite.Draw(spriteBatch,
new Vector2(body.DrawPosition.X, -body.DrawPosition.Y),
new Color((color.R * wearableColor.R) / (255.0f * 255.0f), (color.G * wearableColor.G) / (255.0f * 255.0f), (color.B * wearableColor.B) / (255.0f * 255.0f)) * ((color.A * wearableColor.A) / (255.0f * 255.0f)),
origin, -body.DrawRotation,
Scale * textureScale, spriteEffect, depth);
}
private WearableSprite GetWearableSprite(WearableType type, bool random = false)
{
var info = character.Info;
if (info == null) { return null; }
XElement element;
if (random)
{
element = info.FilterByTypeAndHeadID(character.Info.FilterElementsByGenderAndRace(character.Info.Wearables), type)?.GetRandom(Rand.RandSync.ClientOnly);
}
else
{
element = info.FilterByTypeAndHeadID(character.Info.FilterElementsByGenderAndRace(character.Info.Wearables), type)?.FirstOrDefault();
}
if (element != null)
{
return new WearableSprite(element.Element("sprite"), type);
}
return null;
}
partial void RemoveProjSpecific()
{
Sprite?.Remove();
Sprite = null;
DamagedSprite?.Remove();
DamagedSprite = null;
_deformSprite?.Sprite?.Remove();
_deformSprite = null;
DecorativeSprites.ForEach(s => s.Remove());
ConditionalSprites.Clear();
ConditionalSprites.ForEach(s => s.Remove());
ConditionalSprites.Clear();
LightSource?.Remove();
LightSource = null;
OtherWearables?.ForEach(w => w.Sprite.Remove());
OtherWearables = null;
HuskSprite?.Sprite.Remove();
HuskSprite = null;
HerpesSprite?.Sprite.Remove();
HerpesSprite = null;
}
}
}

View File

@@ -0,0 +1,192 @@
using System.Collections.Generic;
using System.Linq;
using Barotrauma.Networking;
using Microsoft.Xna.Framework.Input;
namespace Barotrauma
{
// TODO decide what folder this falls under. Utils? GUI? or just leave where it is. Also probably a better class name name [<- no need to create a separate namespace, maybe a folder?]
/// <summary>
/// A class used for handling special key actions in chat boxes.
/// For example tab completion or up/down arrow key history.
/// </summary>
public class ChatManager
{
private readonly bool loop;
// Maximum items we want to store in the history
private readonly short maxCount = 10;
// List of previously stored messages
private readonly List<string> messageList = new List<string> { string.Empty };
/// Keep track of the registered fields so we don't register them twice
/// I couldn't figure out where to register this in <see cref="NetLobbyScreen"/> where it wouldn't register twice
/// It's probably not the most optimal way of doing this so feel free to change this
/// <seealso cref="NetLobbyScreen.Select"/> where I'm utilizing this
private readonly List<GUITextBox> registers = new List<GUITextBox>();
// Selector index
private int index;
// Local changes we've made into previously stored messages
private string[] localChanges;
public ChatManager(bool loop, short maxCount)
{
this.loop = loop;
this.maxCount = maxCount;
localChanges = new string[maxCount];
}
public ChatManager()
{
localChanges = new string[maxCount];
}
/// <summary>
/// Registers special input actions to the selected input field
/// </summary>
/// <param name="element">GUI Element we want to register</param>
/// <param name="manager">Instance</param>
public static void RegisterKeys(GUITextBox element, ChatManager manager)
{
// If already registered then don't register it again
if (manager.registers.Any(p => element == p)) { return; }
element.OnKeyHit += (sender, key) =>
{
switch (key)
{
// Up/Down Arrow key history action
case Keys.Up:
case Keys.Down:
{
// Up arrow key? go up. Down arrow key? go down. Everything else gets binned
Direction direction = key == Keys.Up ? Direction.Up : (key == Keys.Down ? Direction.Down : Direction.Other);
string newMessage = manager.SelectMessage(direction, element.Text);
// Don't do anything if we didn't find anything
if (newMessage == null) { return; }
element.Text = newMessage;
break;
}
case Keys.Tab:
// TODO tab completion behavior, maybe?
break;
}
};
manager.registers.Add(element);
}
// Store a new message
public void Store(string message)
{
Clear();
var strip = StripMessage(message);
if (string.IsNullOrWhiteSpace(strip)) { return; }
if (messageList.Count > 1 && messageList[1] == message) { return; }
// insert to the second position as the first position is reserved for the original message if any
messageList.Insert(1, message);
// we don't want to add too many messages
if (messageList.Count > maxCount)
{
messageList.RemoveAt(messageList.Count - 1);
}
// [It's also possible to lambdas too in short methods, if you like: string StripMessage(string text) => ChatMessage.GetChatMessageCommand(text, out string msg);]
static string StripMessage(string text)
{
ChatMessage.GetChatMessageCommand(text, out string msg);
return msg;
}
}
/// <summary>
/// Call this function whenever we should stop doing special stuff and return normal behavior.
/// For example when you deselect the chat box.
/// </summary>
public void Clear()
{
index = 0;
localChanges = new string[maxCount];
}
/// <summary>
/// Scroll up or down on the message history and return a message
/// </summary>
/// <param name="direction">Direction we want to scroll the stack</param>
/// <param name="original">Leftover text that is in the chat box when we override it</param>
/// <returns>A message or null</returns>
private string SelectMessage(Direction direction, string original)
{
var originalIndex = index;
while (true)
{
if (direction == Direction.Other)
{
return null;
}
// temporarily save our changes in case we fat-finger and want to go back
localChanges[index] = original;
var dir = (int) direction;
var nextIndex = (index + dir);
if (loop && messageList.Count > 1)
{
nextIndex = LoopAround(nextIndex);
}
else
{
if (nextIndex > messageList.Count - 1)
{
return null;
}
}
if (nextIndex >= 0 && EntryAt(nextIndex) == original && nextIndex != originalIndex && originalIndex != 0)
{
index = nextIndex;
continue;
}
return nextIndex < 0 ? localChanges.FirstOrDefault() : EntryAt(index = nextIndex);
string EntryAt(int i)
{
// if we've previously edited the entry then give us that, else give us the original message
return localChanges[i] ?? messageList[i];
}
int LoopAround(int next)
{
if (next > (messageList.Count - 1))
{
return 1;
}
if (next < 1)
{
return messageList.Count - 1;
}
return next;
}
}
}
// Used for Up/Down arrow key action
private enum Direction
{
Up = 1,
Down = -1,
Other = 0
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,129 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
namespace Barotrauma
{
partial class EventManager
{
private Graph intensityGraph;
private Graph targetIntensityGraph;
private float intensityGraphUpdateInterval;
private float lastIntensityUpdate;
public void DebugDraw(SpriteBatch spriteBatch)
{
foreach (ScriptedEvent ev in activeEvents)
{
Vector2 drawPos = ev.DebugDrawPos;
drawPos.Y = -drawPos.Y;
var textOffset = new Vector2(-150, 0);
ShapeExtensions.DrawCircle(spriteBatch, drawPos, 600, 6, Color.White, thickness: 20);
GUI.DrawString(spriteBatch, drawPos + textOffset, ev.ToString(), Color.White, Color.Black, 0, GUI.LargeFont);
}
}
public void DebugDrawHUD(SpriteBatch spriteBatch, int y)
{
GUI.DrawString(spriteBatch, new Vector2(10, y), "EventManager", Color.White, Color.Black * 0.6f, 0, GUI.SmallFont);
GUI.DrawString(spriteBatch, new Vector2(15, y + 20), "Event cooldown: " + eventCoolDown, Color.White, Color.Black * 0.6f, 0, GUI.SmallFont);
GUI.DrawString(spriteBatch, new Vector2(15, y + 35), "Current intensity: " + (int)Math.Round(currentIntensity * 100), Color.Lerp(Color.White, GUI.Style.Red, currentIntensity), Color.Black * 0.6f, 0, GUI.SmallFont);
GUI.DrawString(spriteBatch, new Vector2(15, y + 50), "Target intensity: " + (int)Math.Round(targetIntensity * 100), Color.Lerp(Color.White, GUI.Style.Red, targetIntensity), Color.Black * 0.6f, 0, GUI.SmallFont);
GUI.DrawString(spriteBatch, new Vector2(15, y + 65), "AvgHealth: " + (int)Math.Round(avgCrewHealth * 100), Color.Lerp(GUI.Style.Red, GUI.Style.Green, avgCrewHealth), Color.Black * 0.6f, 0, GUI.SmallFont);
GUI.DrawString(spriteBatch, new Vector2(15, y + 80), "AvgHullIntegrity: " + (int)Math.Round(avgHullIntegrity * 100), Color.Lerp(GUI.Style.Red, GUI.Style.Green, avgHullIntegrity), Color.Black * 0.6f, 0, GUI.SmallFont);
GUI.DrawString(spriteBatch, new Vector2(15, y + 95), "FloodingAmount: " + (int)Math.Round(floodingAmount * 100), Color.Lerp(GUI.Style.Green, GUI.Style.Red, floodingAmount), Color.Black * 0.6f, 0, GUI.SmallFont);
GUI.DrawString(spriteBatch, new Vector2(15, y + 110), "FireAmount: " + (int)Math.Round(fireAmount * 100), Color.Lerp(GUI.Style.Green, GUI.Style.Red, fireAmount), Color.Black * 0.6f, 0, GUI.SmallFont);
GUI.DrawString(spriteBatch, new Vector2(15, y + 125), "EnemyDanger: " + (int)Math.Round(enemyDanger * 100), Color.Lerp(GUI.Style.Green, GUI.Style.Red, enemyDanger), Color.Black * 0.6f, 0, GUI.SmallFont);
#if DEBUG
if (PlayerInput.KeyDown(Microsoft.Xna.Framework.Input.Keys.LeftAlt) &&
PlayerInput.KeyHit(Microsoft.Xna.Framework.Input.Keys.T))
{
eventCoolDown = 1.0f;
}
#endif
if (intensityGraph == null)
{
intensityGraph = new Graph();
targetIntensityGraph = new Graph();
}
intensityGraphUpdateInterval = 5.0f;
if (Timing.TotalTime > lastIntensityUpdate + intensityGraphUpdateInterval)
{
intensityGraph.Update(currentIntensity);
targetIntensityGraph.Update(targetIntensity);
lastIntensityUpdate = (float)Timing.TotalTime;
}
Rectangle graphRect = new Rectangle(15, y + 150, 150, 50);
GUI.DrawRectangle(spriteBatch, graphRect, Color.Black * 0.5f, true);
intensityGraph.Draw(spriteBatch, graphRect, 1.0f, 0.0f, Color.Lerp(Color.White, GUI.Style.Red, currentIntensity));
targetIntensityGraph.Draw(spriteBatch, graphRect, 1.0f, 0.0f, Color.Lerp(Color.White, GUI.Style.Red, targetIntensity) * 0.5f);
GUI.DrawLine(spriteBatch,
new Vector2(graphRect.Right, graphRect.Y + graphRect.Height * (1.0f - eventThreshold)),
new Vector2(graphRect.Right + 5, graphRect.Y + graphRect.Height * (1.0f - eventThreshold)), Color.Orange, 0, 1);
y = graphRect.Bottom + 20;
if (eventCoolDown > 0.0f)
{
GUI.DrawString(spriteBatch, new Vector2(graphRect.X, y), "Event cooldown active: " + (int)eventCoolDown, Color.LightGreen * 0.8f, null, 0, GUI.SmallFont);
y += 15;
}
else if (currentIntensity > eventThreshold)
{
GUI.DrawString(spriteBatch, new Vector2(graphRect.X, y),
"Intensity too high for new events: " + (int)(currentIntensity * 100) + "%/" + (int)(eventThreshold * 100) + "%", Color.LightGreen * 0.8f, null, 0, GUI.SmallFont);
y += 15;
}
foreach (ScriptedEventSet eventSet in pendingEventSets)
{
float distanceTraveled = MathHelper.Clamp(
(Submarine.MainSub.WorldPosition.X - level.StartPosition.X) / (level.EndPosition.X - level.StartPosition.X),
0.0f, 1.0f);
GUI.DrawString(spriteBatch, new Vector2(graphRect.X, y), "New event (ID " + eventSet.DebugIdentifier + ") after: ", Color.Orange * 0.8f, null, 0, GUI.SmallFont);
y += 12;
if ((Submarine.MainSub == null || distanceTraveled < eventSet.MinDistanceTraveled) &&
roundDuration < eventSet.MinMissionTime)
{
GUI.DrawString(spriteBatch, new Vector2(graphRect.X, y),
" " + (int)(eventSet.MinDistanceTraveled * 100.0f) + "% travelled (current: " + (int)(distanceTraveled * 100.0f) + " %)",
Color.Orange * 0.8f, null, 0, GUI.SmallFont);
y += 12;
}
if (CurrentIntensity < eventSet.MinIntensity || CurrentIntensity > eventSet.MaxIntensity)
{
GUI.DrawString(spriteBatch, new Vector2(graphRect.X, y),
" intensity between " + ((int)eventSet.MinIntensity) + " and " + ((int)eventSet.MaxIntensity),
Color.Orange * 0.8f, null, 0, GUI.SmallFont);
y += 12;
}
if (roundDuration < eventSet.MinMissionTime)
{
GUI.DrawString(spriteBatch, new Vector2(graphRect.X, y),
" " + (int)(eventSet.MinMissionTime - roundDuration) + " s",
Color.Orange * 0.8f, null, 0, GUI.SmallFont);
}
y += 15;
}
GUI.DrawString(spriteBatch, new Vector2(graphRect.X, y), "Current events: ", Color.White * 0.9f, null, 0, GUI.SmallFont);
y += 12;
foreach (ScriptedEvent scriptedEvent in activeEvents)
{
if (scriptedEvent.IsFinished) { continue; }
GUI.DrawString(spriteBatch, new Vector2(graphRect.X + 5, y), scriptedEvent.ToString(), Color.White * 0.8f, null, 0, GUI.SmallFont);
y += 12;
}
}
}
}

View File

@@ -0,0 +1,25 @@
using Barotrauma.Networking;
namespace Barotrauma
{
partial class CargoMission : Mission
{
public override void ClientReadInitial(IReadMessage msg)
{
ushort itemCount = msg.ReadUInt16();
for (int i = 0; i < itemCount; i++)
{
items.Add(Item.ReadSpawnData(msg));
}
if (items.Contains(null))
{
throw new System.Exception("Error in CargoMission.ClientReadInitial: item list contains null (mission: " + Prefab.Identifier + ")");
}
if (items.Count != itemCount)
{
throw new System.Exception("Error in CargoMission.ClientReadInitial: item count does not match the server count (" + itemCount + " != " + items.Count + "mission: " + Prefab.Identifier + ")");
}
if (requiredDeliveryAmount == 0) { requiredDeliveryAmount = items.Count; }
}
}
}

View File

@@ -0,0 +1,29 @@
using Barotrauma.Networking;
namespace Barotrauma
{
partial class CombatMission
{
public override string Description
{
get
{
if (descriptions == null) return "";
if (GameMain.Client?.Character == null)
{
//non-team-specific description
return descriptions[0];
}
//team specific
return descriptions[GameMain.Client.Character.TeamID == Character.TeamType.Team1 ? 1 : 2];
}
}
public override void ClientReadInitial(IReadMessage msg)
{
//do nothing
}
}
}

View File

@@ -0,0 +1,29 @@
using Barotrauma.Networking;
namespace Barotrauma
{
partial class Mission
{
partial void ShowMessageProjSpecific(int missionState)
{
int messageIndex = missionState - 1;
if (messageIndex >= Headers.Count && messageIndex >= Messages.Count) { return; }
if (messageIndex < 0) { return; }
string header = messageIndex < Headers.Count ? Headers[messageIndex] : "";
string message = messageIndex < Messages.Count ? Messages[messageIndex] : "";
new GUIMessageBox(header, message, buttons: new string[0], type: GUIMessageBox.Type.InGame, icon: Prefab.Icon)
{
IconColor = Prefab.IconColor
};
}
public void ClientRead(IReadMessage msg)
{
State = msg.ReadInt16();
}
public abstract void ClientReadInitial(IReadMessage msg);
}
}

View File

@@ -0,0 +1,33 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Text;
using System.Xml.Linq;
namespace Barotrauma
{
partial class MissionPrefab
{
public Sprite Icon
{
get;
private set;
}
public Color IconColor
{
get;
private set;
}
partial void InitProjSpecific(XElement element)
{
foreach (XElement subElement in element.Elements())
{
if (!subElement.Name.ToString().Equals("icon", StringComparison.OrdinalIgnoreCase)) { continue; }
Icon = new Sprite(subElement);
IconColor = subElement.GetAttributeColor("color", Color.White);
}
}
}
}

View File

@@ -0,0 +1,25 @@
using Barotrauma.Networking;
namespace Barotrauma
{
partial class MonsterMission : Mission
{
public override void ClientReadInitial(IReadMessage msg)
{
byte monsterCount = msg.ReadByte();
for (int i = 0; i < monsterCount; i++)
{
monsters.Add(Character.ReadSpawnData(msg));
}
if (monsters.Contains(null))
{
throw new System.Exception("Error in MonsterMission.ClientReadInitial: monster list contains null (mission: " + Prefab.Identifier + ")");
}
if (monsters.Count != monsterCount)
{
throw new System.Exception("Error in MonsterMission.ClientReadInitial: monster count does not match the server count (" + monsterCount + " != " + monsters.Count + "mission: " + Prefab.Identifier + ")");
}
InitializeMonsters(monsters);
}
}
}

View File

@@ -0,0 +1,19 @@
using Barotrauma.Networking;
using FarseerPhysics;
namespace Barotrauma
{
partial class SalvageMission : Mission
{
public override void ClientReadInitial(IReadMessage msg)
{
item = Item.ReadSpawnData(msg);
if (item == null)
{
throw new System.Exception("Error in SalvageMission.ClientReadInitial: spawned item was null (mission: " + Prefab.Identifier + ")");
}
item.body.FarseerBody.BodyType = BodyType.Kinematic;
}
}
}

View File

@@ -0,0 +1,20 @@
using Microsoft.Xna.Framework;
using System;
namespace Barotrauma.Extensions
{
public static class ColorExtensions
{
public static Color Multiply(this Color color, float value, bool onlyAlpha = false)
{
return onlyAlpha ?
new Color(color.R, color.G, color.B, (byte)(color.A * value)) :
new Color((byte)(color.R * value), (byte)(color.G * value), (byte)(color.B * value), (byte)(color.A * value));
}
public static Color Opaque(this Color color)
{
return new Color(color.R, color.G, color.B, (byte)255);
}
}
}

View File

@@ -0,0 +1,618 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using SharpFont;
using System;
using System.Collections.Generic;
using System.Xml.Linq;
namespace Barotrauma
{
public class ColorData
{
public int StartIndex, EndIndex;
public Color Color;
private const char colorDefinitionIndicator = '‖';
private const char lineChangeIndicator = '\n';
private const string colorDefinitionStartString = "‖color:";
private const string coloringEndDefinition = "‖color:end‖";
public static List<ColorData> GetColorData(string text, out string sanitizedText)
{
List<ColorData> textColors = null;
if (text != null && text.IndexOf(colorDefinitionIndicator) != -1 && text.Contains(colorDefinitionStartString))
{
textColors = new List<ColorData>();
List<int> lineChangeIndexes = null;
int currentIndex = text.IndexOf(lineChangeIndicator);
if (currentIndex != -1)
{
lineChangeIndexes = new List<int>();
lineChangeIndexes.Add(currentIndex);
int startIndex = currentIndex + 1;
while (true)
{
if (startIndex >= text.Length) break;
currentIndex = text.IndexOf(lineChangeIndicator, startIndex);
if (currentIndex == -1) break;
lineChangeIndexes.Add(currentIndex);
startIndex = currentIndex + 1;
}
}
while (text.IndexOf(colorDefinitionStartString) != -1)
{
ColorData colorData = new ColorData();
int colorDefinitionStartIndex = text.IndexOf(colorDefinitionStartString);
int colorDefinitionEndIndex = text.IndexOf(colorDefinitionIndicator, colorDefinitionStartIndex + 1);
string[] colorDefinition = text.Substring(colorDefinitionStartIndex + colorDefinitionStartString.Length, colorDefinitionEndIndex - colorDefinitionStartIndex - colorDefinitionStartString.Length).Split(',');
colorData.StartIndex = colorDefinitionStartIndex;
colorData.Color = new Color(int.Parse(colorDefinition[0]), int.Parse(colorDefinition[1]), int.Parse(colorDefinition[2]));
text = text.Remove(colorDefinitionStartIndex, colorDefinitionEndIndex - colorDefinitionStartIndex + 1);
colorData.EndIndex = text.IndexOf(coloringEndDefinition);
text = text.Remove(colorData.EndIndex, coloringEndDefinition.Length);
if (lineChangeIndexes != null)
{
for (int i = 0; i < lineChangeIndexes.Count; i++)
{
if (colorData.StartIndex > lineChangeIndexes[i])
{
colorData.StartIndex--;
colorData.EndIndex--;
}
}
}
textColors.Add(colorData);
}
}
sanitizedText = text;
return textColors;
}
}
public class ScalableFont : IDisposable
{
private static List<ScalableFont> FontList = new List<ScalableFont>();
private static Library Lib = null;
private static object mutex = new object();
private string filename;
private Face face;
private uint size;
private int baseHeight;
//private int lineHeight;
private Dictionary<uint, GlyphData> texCoords;
private List<Texture2D> textures;
private GraphicsDevice graphicsDevice;
private Vector2 currentDynamicAtlasCoords;
private int currentDynamicAtlasNextY;
uint[] currentDynamicPixelBuffer;
public bool DynamicLoading
{
get;
private set;
}
public bool IsCJK
{
get;
private set;
}
public uint Size
{
get
{
return size;
}
set
{
size = value;
if (graphicsDevice != null) RenderAtlas(graphicsDevice, charRanges, texDims, baseChar);
}
}
private uint[] charRanges;
private int texDims;
private uint baseChar;
private struct GlyphData
{
public int texIndex;
public Vector2 drawOffset;
public float advance;
public Rectangle texCoords;
}
public ScalableFont(XElement element, GraphicsDevice gd = null)
: this(
element.GetAttributeString("file", ""),
(uint)element.GetAttributeInt("size", 14),
gd,
element.GetAttributeBool("dynamicloading", false),
element.GetAttributeBool("iscjk", false))
{
}
public ScalableFont(string filename, uint size, GraphicsDevice gd = null, bool dynamicLoading = false, bool isCJK = false)
{
lock (mutex)
{
if (Lib == null) Lib = new Library();
this.filename = filename;
this.face = null;
foreach (ScalableFont font in FontList)
{
if (font.filename == filename)
{
this.face = font.face;
break;
}
}
if (this.face == null)
{
this.face = new Face(Lib, filename);
}
this.size = size;
this.textures = new List<Texture2D>();
this.texCoords = new Dictionary<uint, GlyphData>();
this.DynamicLoading = dynamicLoading;
this.IsCJK = isCJK;
this.graphicsDevice = gd;
if (gd != null && !dynamicLoading)
{
RenderAtlas(gd);
}
FontList.Add(this);
}
}
/// <summary>
/// Renders the font into at least one texture atlas, which is simply a collection of all glyphs in the ranges defined by charRanges.
/// Don't call this too often or with very large sizes.
/// </summary>
/// <param name="gd">Graphics device, required to create textures.</param>
/// <param name="charRanges">Character ranges between each even element with their corresponding odd element. Default is 0x20 to 0xFFFF.</param>
/// <param name="texDims">Texture dimensions. Default is 512x512.</param>
/// <param name="baseChar">Base character used to shift all other characters downwards when rendering. Defaults to T.</param>
public void RenderAtlas(GraphicsDevice gd, uint[] charRanges = null, int texDims = 1024, uint baseChar = 0x54)
{
if (DynamicLoading) { return; }
if (charRanges == null)
{
charRanges = new uint[] { 0x20, 0xFFFF };
}
this.charRanges = charRanges;
this.texDims = texDims;
this.baseChar = baseChar;
textures.ForEach(t => t.Dispose());
textures.Clear();
texCoords.Clear();
uint[] pixelBuffer = new uint[texDims * texDims];
for (int i = 0; i < texDims * texDims; i++)
{
pixelBuffer[i] = 0;
}
CrossThread.RequestExecutionOnMainThread(() =>
{
textures.Add(new Texture2D(gd, texDims, texDims, false, SurfaceFormat.Color));
});
int texIndex = 0;
Vector2 currentCoords = Vector2.Zero;
int nextY = 0;
lock (mutex)
{
face.SetPixelSizes(0, size);
face.LoadGlyph(face.GetCharIndex(baseChar), LoadFlags.Default, LoadTarget.Normal);
baseHeight = face.Glyph.Metrics.Height.ToInt32();
for (int i = 0; i < charRanges.Length; i += 2)
{
uint start = charRanges[i];
uint end = charRanges[i + 1];
for (uint j = start; j <= end; j++)
{
uint glyphIndex = face.GetCharIndex(j);
if (glyphIndex == 0) continue;
face.LoadGlyph(glyphIndex, LoadFlags.Default, LoadTarget.Normal);
if (face.Glyph.Metrics.Width == 0 || face.Glyph.Metrics.Height == 0)
{
if (face.Glyph.Metrics.HorizontalAdvance > 0)
{
//glyph is empty, but char still applies advance
GlyphData blankData = new GlyphData();
blankData.advance = (float)face.Glyph.Metrics.HorizontalAdvance;
blankData.texIndex = -1; //indicates no texture because the glyph is empty
texCoords.Add(j, blankData);
}
continue;
}
//stacktrace doesn't really work that well when RenderGlyph throws an exception
face.Glyph.RenderGlyph(RenderMode.Normal);
byte[] bitmap = face.Glyph.Bitmap.BufferData;
int glyphWidth = face.Glyph.Bitmap.Width;
int glyphHeight = bitmap.Length / glyphWidth;
//if (glyphHeight>lineHeight) lineHeight=glyphHeight;
if (glyphWidth > texDims - 1 || glyphHeight > texDims - 1)
{
throw new Exception(filename + ", " + size.ToString() + ", " + (char)j + "; Glyph dimensions exceed texture atlas dimensions");
}
nextY = Math.Max(nextY, glyphHeight + 2);
if (currentCoords.X + glyphWidth + 2 > texDims - 1)
{
currentCoords.X = 0;
currentCoords.Y += nextY;
nextY = 0;
}
if (currentCoords.Y + glyphHeight + 2 > texDims - 1)
{
currentCoords.X = 0;
currentCoords.Y = 0;
CrossThread.RequestExecutionOnMainThread(() =>
{
textures[texIndex].SetData<uint>(pixelBuffer);
textures.Add(new Texture2D(gd, texDims, texDims, false, SurfaceFormat.Color));
});
texIndex++;
for (int k = 0; k < texDims * texDims; k++)
{
pixelBuffer[k] = 0;
}
}
GlyphData newData = new GlyphData
{
advance = (float)face.Glyph.Metrics.HorizontalAdvance,
texIndex = texIndex,
texCoords = new Rectangle((int)currentCoords.X, (int)currentCoords.Y, glyphWidth, glyphHeight),
drawOffset = new Vector2(face.Glyph.BitmapLeft, baseHeight * 14 / 10 - face.Glyph.BitmapTop)
};
texCoords.Add(j, newData);
for (int y = 0; y < glyphHeight; y++)
{
for (int x = 0; x < glyphWidth; x++)
{
byte byteColor = bitmap[x + y * glyphWidth];
pixelBuffer[((int)currentCoords.X + x) + ((int)currentCoords.Y + y) * texDims] = (uint)(byteColor << 24 | 0x00ffffff);
}
}
currentCoords.X += glyphWidth + 2;
}
CrossThread.RequestExecutionOnMainThread(() =>
{
textures[texIndex].SetData<uint>(pixelBuffer);
});
}
}
}
public void DynamicRenderAtlas(GraphicsDevice gd, uint character, int texDims = 1024, uint baseChar = 0x54)
{
if (System.Threading.Thread.CurrentThread != GameMain.MainThread)
{
CrossThread.RequestExecutionOnMainThread(() =>
{
DynamicRenderAtlas(gd, character, texDims, baseChar);
});
return;
}
byte[] bitmap;
int glyphWidth; int glyphHeight;
Fixed26Dot6 horizontalAdvance;
Vector2 drawOffset;
lock (mutex)
{
if (texCoords.ContainsKey(character)) { return; }
if (textures.Count == 0)
{
this.texDims = texDims;
this.baseChar = baseChar;
face.SetPixelSizes(0, size);
face.LoadGlyph(face.GetCharIndex(baseChar), LoadFlags.Default, LoadTarget.Normal);
baseHeight = face.Glyph.Metrics.Height.ToInt32();
textures.Add(new Texture2D(gd, texDims, texDims, false, SurfaceFormat.Color));
}
uint glyphIndex = face.GetCharIndex(character);
if (glyphIndex == 0) { return; }
face.SetPixelSizes(0, size);
face.LoadGlyph(glyphIndex, LoadFlags.Default, LoadTarget.Normal);
if (face.Glyph.Metrics.Width == 0 || face.Glyph.Metrics.Height == 0)
{
if (face.Glyph.Metrics.HorizontalAdvance > 0)
{
//glyph is empty, but char still applies advance
GlyphData blankData = new GlyphData();
blankData.advance = (float)face.Glyph.Metrics.HorizontalAdvance;
blankData.texIndex = -1; //indicates no texture because the glyph is empty
texCoords.Add(character, blankData);
}
return;
}
//stacktrace doesn't really work that well when RenderGlyph throws an exception
face.Glyph.RenderGlyph(RenderMode.Normal);
bitmap = (byte[])face.Glyph.Bitmap.BufferData.Clone();
glyphWidth = face.Glyph.Bitmap.Width;
glyphHeight = bitmap.Length / glyphWidth;
horizontalAdvance = face.Glyph.Metrics.HorizontalAdvance;
drawOffset = new Vector2(face.Glyph.BitmapLeft, baseHeight * 14 / 10 - face.Glyph.BitmapTop);
if (glyphWidth > texDims - 1 || glyphHeight > texDims - 1)
{
throw new Exception(filename + ", " + size.ToString() + ", " + (char)character + "; Glyph dimensions exceed texture atlas dimensions");
}
currentDynamicAtlasNextY = Math.Max(currentDynamicAtlasNextY, glyphHeight + 2);
if (currentDynamicAtlasCoords.X + glyphWidth + 2 > texDims - 1)
{
currentDynamicAtlasCoords.X = 0;
currentDynamicAtlasCoords.Y += currentDynamicAtlasNextY;
currentDynamicAtlasNextY = 0;
}
//no more room in current texture atlas, create a new one
if (currentDynamicAtlasCoords.Y + glyphHeight + 2 > texDims - 1)
{
currentDynamicAtlasCoords.X = 0;
currentDynamicAtlasCoords.Y = 0;
currentDynamicAtlasNextY = 0;
textures.Add(new Texture2D(gd, texDims, texDims, false, SurfaceFormat.Color));
currentDynamicPixelBuffer = null;
}
GlyphData newData = new GlyphData
{
advance = (float)horizontalAdvance,
texIndex = textures.Count - 1,
texCoords = new Rectangle((int)currentDynamicAtlasCoords.X, (int)currentDynamicAtlasCoords.Y, glyphWidth, glyphHeight),
drawOffset = drawOffset
};
texCoords.Add(character, newData);
if (currentDynamicPixelBuffer == null)
{
currentDynamicPixelBuffer = new uint[texDims * texDims];
textures[newData.texIndex].GetData<uint>(currentDynamicPixelBuffer, 0, texDims * texDims);
}
for (int y = 0; y < glyphHeight; y++)
{
for (int x = 0; x < glyphWidth; x++)
{
byte byteColor = bitmap[x + y * glyphWidth];
currentDynamicPixelBuffer[((int)currentDynamicAtlasCoords.X + x) + ((int)currentDynamicAtlasCoords.Y + y) * texDims] = (uint)(byteColor << 24 | 0x00ffffff);
}
}
textures[newData.texIndex].SetData<uint>(currentDynamicPixelBuffer);
currentDynamicAtlasCoords.X += glyphWidth + 2;
}
}
public void DrawString(SpriteBatch sb, string text, Vector2 position, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects se, float layerDepth)
{
if (textures.Count == 0 && !DynamicLoading) { return; }
int lineNum = 0;
Vector2 currentPos = position;
Vector2 advanceUnit = rotation == 0.0f ? Vector2.UnitX : new Vector2((float)Math.Cos(rotation), (float)Math.Sin(rotation));
for (int i = 0; i < text.Length; i++)
{
if (text[i] == '\n')
{
lineNum++;
currentPos = position;
currentPos.X -= baseHeight * 1.8f * lineNum * advanceUnit.Y * scale.Y;
currentPos.Y += baseHeight * 1.8f * lineNum * advanceUnit.X * scale.Y;
continue;
}
uint charIndex = text[i];
if (DynamicLoading)
{
DynamicRenderAtlas(graphicsDevice, charIndex);
}
if (texCoords.TryGetValue(charIndex, out GlyphData gd) || texCoords.TryGetValue(9633, out gd)) //9633 = white square
{
if (gd.texIndex >= 0)
{
Texture2D tex = textures[gd.texIndex];
Vector2 drawOffset;
drawOffset.X = gd.drawOffset.X * advanceUnit.X * scale.X - gd.drawOffset.Y * advanceUnit.Y * scale.Y;
drawOffset.Y = gd.drawOffset.X * advanceUnit.Y * scale.Y + gd.drawOffset.Y * advanceUnit.X * scale.X;
sb.Draw(tex, currentPos + drawOffset, gd.texCoords, color, rotation, origin, scale, se, layerDepth);
}
currentPos += gd.advance * advanceUnit * scale.X;
}
}
}
public void DrawString(SpriteBatch sb, string text, Vector2 position, Color color, float rotation, Vector2 origin, float scale, SpriteEffects se, float layerDepth)
{
DrawString(sb, text, position, color, rotation, origin, new Vector2(scale), se, layerDepth);
}
public void DrawString(SpriteBatch sb, string text, Vector2 position, Color color)
{
if (textures.Count == 0 && !DynamicLoading) { return; }
Vector2 currentPos = position;
for (int i = 0; i < text.Length; i++)
{
if (text[i] == '\n')
{
currentPos.X = position.X;
currentPos.Y += baseHeight * 1.8f;
continue;
}
uint charIndex = text[i];
if (DynamicLoading)
{
DynamicRenderAtlas(graphicsDevice, charIndex);
}
if (texCoords.TryGetValue(charIndex, out GlyphData gd) || texCoords.TryGetValue(9633, out gd)) //9633 = white square
{
if (gd.texIndex >= 0)
{
Texture2D tex = textures[gd.texIndex];
sb.Draw(tex, currentPos + gd.drawOffset, gd.texCoords, color);
}
currentPos.X += gd.advance;
}
}
}
public void DrawStringWithColors(SpriteBatch sb, string text, Vector2 position, Color color, float rotation, Vector2 origin, float scale, SpriteEffects se, float layerDepth, List<ColorData> colorData)
{
DrawStringWithColors(sb, text, position, color, rotation, origin, new Vector2(scale), se, layerDepth, colorData);
}
public void DrawStringWithColors(SpriteBatch sb, string text, Vector2 position, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects se, float layerDepth, List<ColorData> colorData)
{
if (textures.Count == 0 && !DynamicLoading) { return; }
int lineNum = 0;
Vector2 currentPos = position;
Vector2 advanceUnit = rotation == 0.0f ? Vector2.UnitX : new Vector2((float)Math.Cos(rotation), (float)Math.Sin(rotation));
int colorDataIndex = 0;
ColorData currentColorData = colorData[colorDataIndex];
for (int i = 0; i < text.Length; i++)
{
if (text[i] == '\n')
{
lineNum++;
currentPos = position;
currentPos.X -= baseHeight * 1.8f * lineNum * advanceUnit.Y * scale.Y;
currentPos.Y += baseHeight * 1.8f * lineNum * advanceUnit.X * scale.Y;
continue;
}
uint charIndex = text[i];
if (DynamicLoading && !texCoords.ContainsKey(charIndex))
{
DynamicRenderAtlas(graphicsDevice, charIndex);
}
Color currentTextColor;
if (currentColorData != null && i > currentColorData.EndIndex + lineNum)
{
colorDataIndex++;
currentColorData = colorDataIndex < colorData.Count ? colorData[colorDataIndex] : null;
}
if (currentColorData != null && currentColorData.StartIndex + lineNum <= i && i <= currentColorData.EndIndex + lineNum)
{
currentTextColor = currentColorData.Color;
}
else
{
currentTextColor = color;
}
if (texCoords.TryGetValue(charIndex, out GlyphData gd) || texCoords.TryGetValue(9633, out gd)) //9633 = white square
{
if (gd.texIndex >= 0)
{
Texture2D tex = textures[gd.texIndex];
Vector2 drawOffset;
drawOffset.X = gd.drawOffset.X * advanceUnit.X * scale.X - gd.drawOffset.Y * advanceUnit.Y * scale.Y;
drawOffset.Y = gd.drawOffset.X * advanceUnit.Y * scale.Y + gd.drawOffset.Y * advanceUnit.X * scale.X;
sb.Draw(tex, currentPos + drawOffset, gd.texCoords, currentTextColor, rotation, origin, scale, se, layerDepth);
}
currentPos += gd.advance * advanceUnit * scale.X;
}
}
}
public Vector2 MeasureString(string text)
{
if (text == null)
{
return Vector2.Zero;
}
float currentLineX = 0.0f;
Vector2 retVal = Vector2.Zero;
retVal.Y = baseHeight * 1.8f;
for (int i = 0; i < text.Length; i++)
{
if (text[i] == '\n')
{
currentLineX = 0.0f;
retVal.Y += baseHeight * 1.8f;
continue;
}
uint charIndex = text[i];
if (DynamicLoading && !texCoords.ContainsKey(charIndex))
{
DynamicRenderAtlas(graphicsDevice, charIndex);
}
if (texCoords.TryGetValue(charIndex, out GlyphData gd))
{
currentLineX += gd.advance;
}
retVal.X = Math.Max(retVal.X, currentLineX);
}
return retVal;
}
public Vector2 MeasureChar(char c)
{
Vector2 retVal = Vector2.Zero;
retVal.Y = baseHeight * 1.8f;
if (DynamicLoading && !texCoords.ContainsKey(c))
{
DynamicRenderAtlas(graphicsDevice, c);
}
if (texCoords.TryGetValue(c, out GlyphData gd))
{
retVal.X = gd.advance;
}
return retVal;
}
public void Dispose()
{
FontList.Remove(this);
foreach (Texture2D texture in textures)
{
texture.Dispose();
}
textures.Clear();
}
}
}

View File

@@ -0,0 +1,409 @@
using Barotrauma.Extensions;
using Barotrauma.Items.Components;
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework.Input;
namespace Barotrauma
{
class ChatBox
{
public const string RadioChatString = "r; ";
private GUIListBox chatBox;
private Point screenResolution;
public readonly ChatManager ChatManager = new ChatManager();
public bool IsSinglePlayer { get; private set; }
private bool _toggleOpen = true;
public bool ToggleOpen
{
get { return _toggleOpen; }
set
{
_toggleOpen = GameMain.Config.ChatOpen = value;
if (value) hideableElements.Visible = true;
}
}
private float openState;
public bool CloseAfterMessageSent;
private float prevUIScale;
//individual message texts that pop up when the chatbox is hidden
const float PopupMessageDuration = 5.0f;
private float popupMessageTimer;
private Queue<GUIComponent> popupMessages = new Queue<GUIComponent>();
public GUITextBox.OnEnterHandler OnEnterMessage
{
get { return InputBox.OnEnterPressed; }
set { InputBox.OnEnterPressed = value; }
}
public GUIFrame GUIFrame { get; private set; }
public GUITextBox InputBox { get; private set; }
public GUIButton ToggleButton;
private GUIButton showNewMessagesButton;
private GUIFrame hideableElements;
public const int ToggleButtonWidthRaw = 30;
private int popupMessageOffset;
public ChatBox(GUIComponent parent, bool isSinglePlayer)
{
this.IsSinglePlayer = isSinglePlayer;
screenResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
int toggleButtonWidth = (int)(ToggleButtonWidthRaw * GUI.Scale);
GUIFrame = new GUIFrame(HUDLayoutSettings.ToRectTransform(HUDLayoutSettings.ChatBoxArea, parent.RectTransform), style: null);
hideableElements = new GUIFrame(new RectTransform(Vector2.One, GUIFrame.RectTransform), style: null);
var chatBoxHolder = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.875f), hideableElements.RectTransform), style: "ChatBox");
chatBox = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.95f), chatBoxHolder.RectTransform, Anchor.CenterRight), style: null);
InputBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 0.125f), hideableElements.RectTransform, Anchor.BottomLeft),
style: "ChatTextBox")
{
OverflowClip = true,
Font = GUI.SmallFont,
MaxTextLength = ChatMessage.MaxLength
};
ChatManager.RegisterKeys(InputBox, ChatManager);
InputBox.OnDeselected += (gui, Keys) =>
{
ChatManager.Clear();
ChatMessage.GetChatMessageCommand(InputBox.Text, out var message);
if (string.IsNullOrEmpty(message))
{
if (CloseAfterMessageSent)
{
_toggleOpen = false;
CloseAfterMessageSent = false;
}
}
//gui.Text = "";
};
var chatSendButton = new GUIButton(new RectTransform(new Vector2(1.0f, 0.7f), InputBox.RectTransform, Anchor.CenterRight, scaleBasis: ScaleBasis.BothHeight), style: "GUIButtonToggleRight");
chatSendButton.OnClicked += (GUIButton btn, object userdata) =>
{
InputBox.OnEnterPressed(InputBox, InputBox.Text);
return true;
};
chatSendButton.RectTransform.AbsoluteOffset = new Point((int)(InputBox.Rect.Height * 0.15f), 0);
InputBox.TextBlock.RectTransform.MaxSize
= new Point((int)(InputBox.Rect.Width - chatSendButton.Rect.Width * 1.25f - InputBox.TextBlock.Padding.Z), int.MaxValue);
showNewMessagesButton = new GUIButton(new RectTransform(new Vector2(1f, 0.075f), GUIFrame.RectTransform, Anchor.BottomCenter) { RelativeOffset = new Vector2(0.0f, 0.125f) }, TextManager.Get("chat.shownewmessages"));
showNewMessagesButton.OnClicked += (GUIButton btn, object userdata) =>
{
chatBox.ScrollBar.BarScrollValue = 1f;
showNewMessagesButton.Visible = false;
return true;
};
showNewMessagesButton.Visible = false;
ToggleOpen = GameMain.Config.ChatOpen;
}
public bool TypingChatMessage(GUITextBox textBox, string text)
{
string command = ChatMessage.GetChatMessageCommand(text, out _);
if (IsSinglePlayer)
{
//radio is the only allowed special message type in single player
if (command != "r" && command != "radio")
{
command = "";
}
}
switch (command)
{
case "r":
case "radio":
textBox.TextColor = ChatMessage.MessageColor[(int)ChatMessageType.Radio];
break;
case "d":
case "dead":
textBox.TextColor = ChatMessage.MessageColor[(int)ChatMessageType.Dead];
break;
default:
if (Character.Controlled != null && (Character.Controlled.IsDead || Character.Controlled.SpeechImpediment >= 100.0f))
{
textBox.TextColor = ChatMessage.MessageColor[(int)ChatMessageType.Dead];
}
else if (command != "") //PMing
{
textBox.TextColor = ChatMessage.MessageColor[(int)ChatMessageType.Private];
}
else
{
textBox.TextColor = ChatMessage.MessageColor[(int)ChatMessageType.Default];
}
break;
}
return true;
}
public void AddMessage(ChatMessage message)
{
while (chatBox.Content.CountChildren > 60)
{
chatBox.RemoveChild(chatBox.Content.Children.First());
}
float prevSize = chatBox.BarSize;
string displayedText = message.TranslatedText;
string senderName = "";
Color senderColor = Color.White;
if (!string.IsNullOrWhiteSpace(message.SenderName))
{
senderName = (message.Type == ChatMessageType.Private ? "[PM] " : "") + message.SenderName;
}
if (message.Sender?.Info?.Job != null)
{
senderColor = Color.Lerp(message.Sender.Info.Job.Prefab.UIColor, Color.White, 0.25f);
}
var msgHolder = new GUIFrame(new RectTransform(new Vector2(0.95f, 0.0f), chatBox.Content.RectTransform, Anchor.TopCenter), style: null,
color: ((chatBox.Content.CountChildren % 2) == 0) ? Color.Transparent : Color.Black * 0.1f);
GUITextBlock senderNameBlock = new GUITextBlock(new RectTransform(new Vector2(0.98f, 0.0f), msgHolder.RectTransform) { AbsoluteOffset = new Point((int)(5 * GUI.Scale), 0) },
ChatMessage.GetTimeStamp(), textColor: Color.LightGray, font: GUI.SmallFont, textAlignment: Alignment.TopLeft, style: null)
{
CanBeFocused = true
};
if (!string.IsNullOrEmpty(senderName))
{
new GUITextBlock(new RectTransform(new Vector2(0.8f, 1.0f), senderNameBlock.RectTransform) { AbsoluteOffset = new Point((int)(senderNameBlock.TextSize.X), 0) },
senderName, textColor: senderColor, font: GUI.SmallFont, textAlignment: Alignment.TopLeft, style: null)
{
CanBeFocused = true
};
}
var msgText =new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), msgHolder.RectTransform)
{ AbsoluteOffset = new Point((int)(10 * GUI.Scale), senderNameBlock == null ? 0 : senderNameBlock.Rect.Height) },
displayedText, textColor: message.Color, font: GUI.SmallFont, textAlignment: Alignment.TopLeft, style: null, wrap: true,
color: ((chatBox.Content.CountChildren % 2) == 0) ? Color.Transparent : Color.Black * 0.1f)
{
UserData = message.SenderName,
CanBeFocused = true
};
if (message is OrderChatMessage orderChatMsg &&
Character.Controlled != null &&
orderChatMsg.TargetCharacter == Character.Controlled)
{
msgHolder.Flash(Color.OrangeRed * 0.6f, flashDuration: 5.0f);
}
else
{
msgHolder.Flash(Color.Yellow * 0.6f);
}
msgHolder.RectTransform.SizeChanged += Recalculate;
Recalculate();
void Recalculate()
{
msgHolder.RectTransform.SizeChanged -= Recalculate;
//resize the holder to match the size of the message and add some spacing
msgText.RectTransform.MaxSize = new Point(msgHolder.Rect.Width - msgText.RectTransform.AbsoluteOffset.X, int.MaxValue);
senderNameBlock.RectTransform.MaxSize = new Point(msgHolder.Rect.Width - senderNameBlock.RectTransform.AbsoluteOffset.X, int.MaxValue);
msgHolder.Children.ForEach(c => (c as GUITextBlock)?.CalculateHeightFromText());
msgHolder.RectTransform.Resize(new Point(msgHolder.Rect.Width, msgHolder.Children.Sum(c => c.Rect.Height) + (int)(10 * GUI.Scale)), resizeChildren: false);
msgHolder.RectTransform.SizeChanged += Recalculate;
chatBox.RecalculateChildren();
chatBox.UpdateScrollBarSize();
}
CoroutineManager.StartCoroutine(UpdateMessageAnimation(msgHolder, 0.5f));
chatBox.UpdateScrollBarSize();
if (chatBox.ScrollBar.Visible && chatBox.ScrollBar.BarScroll < 1f)
{
showNewMessagesButton.Visible = true;
}
if (!ToggleOpen)
{
var popupMsg = new GUIFrame(new RectTransform(Vector2.One, GUIFrame.RectTransform), style: "GUIToolTip")
{
Visible = false,
CanBeFocused = false
};
var content = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), popupMsg.RectTransform, Anchor.Center));
Vector2 senderTextSize = Vector2.Zero;
if (!string.IsNullOrEmpty(senderName))
{
var senderText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform),
senderName, textColor: senderColor, style: null, font: GUI.SmallFont)
{
CanBeFocused = false
};
senderTextSize = senderText.Font.MeasureString(senderText.WrappedText);
senderText.RectTransform.MinSize = new Point(0, senderText.Rect.Height);
}
var msgPopupText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform),
displayedText, textColor: message.Color, font: GUI.SmallFont, textAlignment: Alignment.BottomLeft, style: null, wrap: true)
{
CanBeFocused = false
};
msgPopupText.RectTransform.MinSize = new Point(0, msgPopupText.Rect.Height);
Vector2 msgSize = msgPopupText.Font.MeasureString(msgPopupText.WrappedText);
int textWidth = (int)Math.Max(msgSize.X + msgPopupText.Padding.X + msgPopupText.Padding.Z, senderTextSize.X) + 10;
popupMsg.RectTransform.Resize(new Point((int)(textWidth / content.RectTransform.RelativeSize.X) , (int)((senderTextSize.Y + msgSize.Y) / content.RectTransform.RelativeSize.Y)), resizeChildren: true);
popupMsg.RectTransform.IsFixedSize = true;
content.Recalculate();
popupMessages.Enqueue(popupMsg);
}
if ((prevSize == 1.0f && chatBox.BarScroll == 0.0f) || (prevSize < 1.0f && chatBox.BarScroll == 1.0f)) chatBox.BarScroll = 1.0f;
GUISoundType soundType = GUISoundType.ChatMessage;
if (message.Type == ChatMessageType.Radio)
{
soundType = GUISoundType.RadioMessage;
}
else if (message.Type == ChatMessageType.Dead)
{
soundType = GUISoundType.DeadMessage;
}
GUI.PlayUISound(soundType);
}
public void SetVisibility(bool visible)
{
GUIFrame.Parent.Visible = visible;
}
private IEnumerable<object> UpdateMessageAnimation(GUIComponent message, float animDuration)
{
float timer = 0.0f;
while (timer < animDuration)
{
timer += CoroutineManager.DeltaTime;
float wavePhase = timer / animDuration * MathHelper.TwoPi;
message.RectTransform.ScreenSpaceOffset =
new Point((int)(Math.Sin(wavePhase) * (1.0f - timer / animDuration) * 50.0f), 0);
yield return CoroutineStatus.Running;
}
message.RectTransform.ScreenSpaceOffset = Point.Zero;
yield return CoroutineStatus.Success;
}
private void SetUILayout()
{
GUIFrame.RectTransform.AbsoluteOffset = Point.Zero;
GUIFrame.RectTransform.RelativeOffset = new Vector2(
HUDLayoutSettings.ChatBoxArea.X / (float)GameMain.GraphicsWidth,
HUDLayoutSettings.ChatBoxArea.Y / (float)GameMain.GraphicsHeight);
GUIFrame.RectTransform.NonScaledSize = HUDLayoutSettings.ChatBoxArea.Size;
int toggleButtonWidth = (int)(ToggleButtonWidthRaw * GUI.Scale);
GUIFrame.RectTransform.NonScaledSize -= new Point(toggleButtonWidth, 0);
GUIFrame.RectTransform.AbsoluteOffset += new Point(toggleButtonWidth, 0);
popupMessageOffset = GameMain.GameSession.CrewManager.ReportButtonFrame.Rect.Width + GUIFrame.Rect.Width + (int)(20 * GUI.Scale);
}
public void Update(float deltaTime)
{
if (GameMain.GraphicsWidth != screenResolution.X || GameMain.GraphicsHeight != screenResolution.Y || prevUIScale != GUI.Scale)
{
SetUILayout();
screenResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
prevUIScale = GUI.Scale;
}
//hide chatbox when accessing the inventory of another character to prevent overlaps
if (Character.Controlled?.SelectedCharacter?.Inventory != null &&
Character.Controlled.SelectedCharacter.CanInventoryBeAccessed)
{
SetVisibility(false);
}
else
{
SetVisibility(true);
}
if (showNewMessagesButton.Visible && chatBox.ScrollBar.BarScroll == 1f)
{
showNewMessagesButton.Visible = false;
}
if (ToggleButton != null)
{
ToggleButton.RectTransform.AbsoluteOffset = new Point(GUIFrame.Rect.Right, GUIFrame.Rect.Y + HUDLayoutSettings.ChatBoxArea.Height - ToggleButton.Rect.Height);
}
if (ToggleOpen)
{
openState += deltaTime * 5.0f;
//delete all popup messages when the chatbox is open
while (popupMessages.Count > 0)
{
var popupMsg = popupMessages.Dequeue();
popupMsg.Parent.RemoveChild(popupMsg);
}
}
else
{
openState -= deltaTime * 5.0f;
//make the first popup message visible
var popupMsg = popupMessages.Count > 0 ? popupMessages.Peek() : null;
if (popupMsg != null)
{
popupMsg.Visible = true;
//popup messages appear and disappear faster when there's more pending messages
popupMessageTimer += deltaTime * popupMessages.Count * popupMessages.Count;
if (popupMessageTimer > PopupMessageDuration)
{
//move the message out of the screen and delete it
popupMsg.RectTransform.ScreenSpaceOffset =
new Point((int)MathHelper.SmoothStep(popupMessageOffset, 10, (popupMessageTimer - PopupMessageDuration) * 5.0f), 0);
if (popupMessageTimer > PopupMessageDuration + 1.0f)
{
popupMessageTimer = 0.0f;
popupMsg.Parent.RemoveChild(popupMsg);
popupMessages.Dequeue();
}
}
else
{
//move the message on the screen
popupMsg.RectTransform.ScreenSpaceOffset = new Point(
(int)MathHelper.SmoothStep(0, popupMessageOffset, popupMessageTimer * 5.0f), 0);
}
}
}
openState = MathHelper.Clamp(openState, 0.0f, 1.0f);
int hiddenBoxOffset = -(GUIFrame.Rect.Width);
GUIFrame.RectTransform.AbsoluteOffset =
new Point((int)MathHelper.SmoothStep(hiddenBoxOffset, 0, openState), 0);
hideableElements.Visible = openState > 0.0f;
}
}
}

View File

@@ -0,0 +1,176 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Xml.Linq;
namespace Barotrauma
{
public enum TransitionMode
{
Linear,
Smooth,
Smoother,
EaseIn,
EaseOut,
Exponential
}
public enum SpriteFallBackState
{
None,
Hover,
Pressed,
Selected,
HoverSelected,
Toggle
}
public class GUIComponentStyle
{
public readonly Vector4 Padding;
public readonly Color Color;
public readonly Color HoverColor;
public readonly Color SelectedColor;
public readonly Color PressedColor;
public readonly Color DisabledColor;
public readonly Color TextColor;
public readonly Color HoverTextColor;
public readonly Color SelectedTextColor;
public readonly Color DisabledTextColor;
public readonly float SpriteCrossFadeTime;
public readonly float ColorCrossFadeTime;
public readonly TransitionMode TransitionMode;
public readonly string Font;
public readonly bool ForceUpperCase;
public readonly Color OutlineColor;
public readonly XElement Element;
public readonly Dictionary<GUIComponent.ComponentState, List<UISprite>> Sprites;
public SpriteFallBackState FallBackState;
public Dictionary<string, GUIComponentStyle> ChildStyles;
public readonly GUIStyle Style;
public readonly string Name;
public int? Width { get; private set; }
public int? Height { get; private set; }
public GUIComponentStyle(XElement element, GUIStyle style)
{
Name = element.Name.LocalName;
Style = style;
Element = element;
Sprites = new Dictionary<GUIComponent.ComponentState, List<UISprite>>();
foreach (GUIComponent.ComponentState state in Enum.GetValues(typeof(GUIComponent.ComponentState)))
{
Sprites[state] = new List<UISprite>();
}
ChildStyles = new Dictionary<string, GUIComponentStyle>();
Padding = element.GetAttributeVector4("padding", Vector4.Zero);
Color = element.GetAttributeColor("color", Color.Transparent);
HoverColor = element.GetAttributeColor("hovercolor", Color);
SelectedColor = element.GetAttributeColor("selectedcolor", Color);
DisabledColor = element.GetAttributeColor("disabledcolor", Color);
PressedColor = element.GetAttributeColor("pressedcolor", Color);
OutlineColor = element.GetAttributeColor("outlinecolor", Color.Transparent);
TextColor = element.GetAttributeColor("textcolor", Color.Black);
HoverTextColor = element.GetAttributeColor("hovertextcolor", TextColor);
DisabledTextColor = element.GetAttributeColor("disabledtextcolor", TextColor);
SelectedTextColor = element.GetAttributeColor("selectedtextcolor", TextColor);
SpriteCrossFadeTime = element.GetAttributeFloat("spritefadetime", SpriteCrossFadeTime);
ColorCrossFadeTime = element.GetAttributeFloat("colorfadetime", ColorCrossFadeTime);
if (Enum.TryParse(element.GetAttributeString("colortransition", string.Empty), ignoreCase: true, out TransitionMode transition))
{
TransitionMode = transition;
}
if (Enum.TryParse(element.GetAttributeString("fallbackstate", GUIComponent.ComponentState.None.ToString()), ignoreCase: true, out SpriteFallBackState s))
{
FallBackState = s;
}
Font = element.GetAttributeString("font", "");
ForceUpperCase = element.GetAttributeBool("forceuppercase", false);
foreach (XElement subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "sprite":
UISprite newSprite = new UISprite(subElement);
GUIComponent.ComponentState spriteState = GUIComponent.ComponentState.None;
if (subElement.Attribute("state") != null)
{
string stateStr = subElement.GetAttributeString("state", "None");
Enum.TryParse(stateStr, out spriteState);
Sprites[spriteState].Add(newSprite);
//use the same sprite for Hover and HoverSelected if latter is not specified
if (spriteState == GUIComponent.ComponentState.HoverSelected && !Sprites.ContainsKey(GUIComponent.ComponentState.HoverSelected))
{
Sprites[GUIComponent.ComponentState.HoverSelected].Add(newSprite);
}
}
else
{
foreach (GUIComponent.ComponentState state in Enum.GetValues(typeof(GUIComponent.ComponentState)))
{
Sprites[state].Add(newSprite);
}
}
break;
case "size":
break;
default:
string styleName = subElement.Name.ToString().ToLowerInvariant();
if (ChildStyles.ContainsKey(styleName))
{
DebugConsole.ThrowError("UI style \"" + element.Name.ToString() + "\" contains multiple child styles with the same name (\"" + styleName + "\")!");
ChildStyles[styleName] = new GUIComponentStyle(subElement, style);
}
else
{
ChildStyles.Add(styleName, new GUIComponentStyle(subElement, style));
}
break;
}
}
GetSize(element);
}
public void GetSize(XElement element)
{
Point size = new Point(0, 0);
foreach (XElement subElement in element.Elements())
{
if (!subElement.Name.ToString().Equals("size", StringComparison.OrdinalIgnoreCase)) { continue; }
Point maxResolution = subElement.GetAttributePoint("maxresolution", new Point(int.MaxValue, int.MaxValue));
if (GameMain.GraphicsWidth <= maxResolution.X && GameMain.GraphicsHeight <= maxResolution.Y)
{
size = new Point(
subElement.GetAttributeInt("width", 0),
subElement.GetAttributeInt("height", 0));
break;
}
}
if (size.X > 0) { Width = size.X; }
if (size.Y > 0) { Height = size.Y; }
}
}
}

View File

@@ -0,0 +1,405 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
namespace Barotrauma
{
public static class FileSelection
{
private static bool open;
public static bool Open
{
get
{
return open;
}
set
{
if (value && backgroundFrame == null) { Init(); }
if (!value)
{
fileSystemWatcher?.Dispose();
fileSystemWatcher = null;
}
open = value;
}
}
private static GUIFrame backgroundFrame;
private static GUIFrame window;
private static GUIListBox sidebar;
private static GUIListBox fileList;
private static GUITextBox directoryBox;
private static GUITextBox filterBox;
private static GUITextBox fileBox;
private static GUIDropDown fileTypeDropdown;
private static GUIButton openButton;
private static FileSystemWatcher fileSystemWatcher;
private static string currentFileTypePattern;
private static readonly string[] ignoredDrivePrefixes = new string[]
{
"/sys/", "/snap/"
};
private static string currentDirectory;
public static string CurrentDirectory
{
get
{
return currentDirectory;
}
set
{
string[] dirSplit = value.Replace('\\', '/').Split('/');
List<string> dirs = new List<string>();
for (int i = 0; i < dirSplit.Length; i++)
{
if (dirSplit[i].Trim() == "..")
{
if (dirs.Count > 1)
{
dirs.RemoveAt(dirs.Count - 1);
}
}
else if (dirSplit[i].Trim() != ".")
{
dirs.Add(dirSplit[i]);
}
}
currentDirectory = string.Join("/", dirs);
if (!currentDirectory.EndsWith("/"))
{
currentDirectory += "/";
}
fileSystemWatcher?.Dispose();
fileSystemWatcher = new FileSystemWatcher(currentDirectory)
{
Filter = "*",
NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName
};
fileSystemWatcher.Created += OnFileSystemChanges;
fileSystemWatcher.Deleted += OnFileSystemChanges;
fileSystemWatcher.Renamed += OnFileSystemChanges;
fileSystemWatcher.EnableRaisingEvents = true;
RefreshFileList();
}
}
public static Action<string> OnFileSelected
{
get;
set;
}
private static void OnFileSystemChanges(object sender, FileSystemEventArgs e)
{
switch (e.ChangeType)
{
case WatcherChangeTypes.Created:
{
var itemFrame = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), fileList.Content.RectTransform), e.Name)
{
UserData = (bool?)Directory.Exists(e.FullPath)
};
if ((itemFrame.UserData as bool?) ?? false)
{
itemFrame.Text += "/";
}
fileList.Content.RectTransform.SortChildren(SortFiles);
}
break;
case WatcherChangeTypes.Deleted:
{
var itemFrame = fileList.Content.FindChild(c => (c is GUITextBlock tb) && (tb.Text == e.Name || tb.Text == e.Name + "/"));
if (itemFrame != null) { fileList.RemoveChild(itemFrame); }
}
break;
case WatcherChangeTypes.Renamed:
{
RenamedEventArgs renameArgs = e as RenamedEventArgs;
var itemFrame = fileList.Content.FindChild(c => (c is GUITextBlock tb) && (tb.Text == renameArgs.OldName || tb.Text == renameArgs.OldName + "/")) as GUITextBlock;
itemFrame.UserData = (bool?)Directory.Exists(e.FullPath);
itemFrame.Text = renameArgs.Name;
if ((itemFrame.UserData as bool?) ?? false)
{
itemFrame.Text += "/";
}
fileList.Content.RectTransform.SortChildren(SortFiles);
}
break;
}
}
private static int SortFiles(RectTransform r1, RectTransform r2)
{
string file1 = (r1.GUIComponent as GUITextBlock)?.Text ?? "";
string file2 = (r2.GUIComponent as GUITextBlock)?.Text ?? "";
bool dir1 = (r1.GUIComponent.UserData as bool?) ?? false;
bool dir2 = (r2.GUIComponent.UserData as bool?) ?? false;
if (dir1 && !dir2)
{
return -1;
}
else if (!dir1 && dir2)
{
return 1;
}
return string.Compare(file1, file2);
}
public static void Init()
{
backgroundFrame = new GUIFrame(new RectTransform(Vector2.One, GUI.Canvas), style: null)
{
Color = Color.Black * 0.5f,
HoverColor = Color.Black * 0.5f,
SelectedColor = Color.Black * 0.5f,
PressedColor = Color.Black * 0.5f,
};
window = new GUIFrame(new RectTransform(Vector2.One * 0.8f, backgroundFrame.RectTransform, Anchor.Center));
var horizontalLayout = new GUILayoutGroup(new RectTransform(Vector2.One * 0.9f, window.RectTransform, Anchor.Center), true);
sidebar = new GUIListBox(new RectTransform(new Vector2(0.29f, 1.0f), horizontalLayout.RectTransform));
var drives = DriveInfo.GetDrives();
foreach (var drive in drives)
{
if (drive.DriveType == DriveType.Ram) { continue; }
if (ignoredDrivePrefixes.Any(p => drive.Name.StartsWith(p))) { continue; }
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), sidebar.Content.RectTransform), drive.Name.Replace('\\','/'));
}
sidebar.OnSelected = (child, userdata) =>
{
CurrentDirectory = (child as GUITextBlock).Text;
return false;
};
//spacing between sidebar and fileListLayout
new GUIFrame(new RectTransform(new Vector2(0.01f, 1.0f), horizontalLayout.RectTransform), style: null);
var fileListLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.7f, 1.0f), horizontalLayout.RectTransform));
var firstRow = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.04f), fileListLayout.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft);
new GUIButton(new RectTransform(new Vector2(0.05f, 1.0f), firstRow.RectTransform), "^")
{
OnClicked = MoveToParentDirectory
};
directoryBox = new GUITextBox(new RectTransform(new Vector2(0.7f, 1.0f), firstRow.RectTransform))
{
OverflowClip = true,
OnEnterPressed = (tb, txt) =>
{
if (Directory.Exists(txt))
{
CurrentDirectory = txt;
return true;
}
else
{
tb.Text = CurrentDirectory;
return false;
}
}
};
filterBox = new GUITextBox(new RectTransform(new Vector2(0.25f, 1.0f), firstRow.RectTransform))
{
OverflowClip = true
};
firstRow.RectTransform.MinSize = new Point(0, firstRow.RectTransform.Children.Max(c => c.MinSize.Y));
filterBox.OnTextChanged += (txtbox, txt) =>
{
RefreshFileList();
return true;
};
//spacing between rows
new GUIFrame(new RectTransform(new Vector2(1.0f, 0.01f), fileListLayout.RectTransform), style: null);
fileList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.85f), fileListLayout.RectTransform))
{
OnSelected = (child, userdata) =>
{
if (userdata == null) { return false; }
var fileName = (child as GUITextBlock).Text;
fileBox.Text = fileName;
if (PlayerInput.DoubleClicked())
{
bool isDir = (userdata as bool?).Value;
if (isDir)
{
CurrentDirectory += fileName;
}
else
{
OnFileSelected?.Invoke(CurrentDirectory + fileName);
Open = false;
}
}
return true;
}
};
//spacing between rows
new GUIFrame(new RectTransform(new Vector2(1.0f, 0.01f), fileListLayout.RectTransform), style: null);
var thirdRow = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.04f), fileListLayout.RectTransform), true);
fileBox = new GUITextBox(new RectTransform(new Vector2(0.7f, 1.0f), thirdRow.RectTransform))
{
OnEnterPressed = (tb, txt) => openButton?.OnClicked?.Invoke(openButton, null) ?? false
};
fileTypeDropdown = new GUIDropDown(new RectTransform(new Vector2(0.3f, 1.0f), thirdRow.RectTransform), dropAbove: true)
{
OnSelected = (child, userdata) =>
{
currentFileTypePattern = (child as GUITextBlock).UserData as string;
RefreshFileList();
return true;
}
};
fileTypeDropdown.Select(4);
//spacing between rows
new GUIFrame(new RectTransform(new Vector2(1.0f, 0.01f), fileListLayout.RectTransform), style: null);
var fourthRow = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.04f), fileListLayout.RectTransform), true);
//padding for open/cancel buttons
new GUIFrame(new RectTransform(new Vector2(0.7f, 1.0f), fourthRow.RectTransform), style: null);
openButton = new GUIButton(new RectTransform(new Vector2(0.15f, 1.0f), fourthRow.RectTransform), TextManager.Get("opensubbutton"))
{
OnClicked = (btn, obj) =>
{
if (Directory.Exists(Path.Combine(CurrentDirectory, fileBox.Text)))
{
CurrentDirectory += fileBox.Text;
}
if (!File.Exists(CurrentDirectory + fileBox.Text)) { return false; }
OnFileSelected?.Invoke(CurrentDirectory + fileBox.Text);
Open = false;
return false;
}
};
new GUIButton(new RectTransform(new Vector2(0.15f, 1.0f), fourthRow.RectTransform), TextManager.Get("cancel"))
{
OnClicked = (btn, obj) =>
{
Open = false;
return false;
}
};
CurrentDirectory = Directory.GetCurrentDirectory();
}
public static void ClearFileTypeFilters()
{
if (backgroundFrame == null) { Init(); }
fileTypeDropdown.ClearChildren();
}
public static void AddFileTypeFilter(string name, string pattern)
{
if (backgroundFrame == null) { Init(); }
fileTypeDropdown.AddItem(name + " (" + pattern + ")", pattern);
}
public static void SelectFileTypeFilter(string pattern)
{
if (backgroundFrame == null) { Init(); }
fileTypeDropdown.SelectItem(pattern);
}
public static void RefreshFileList()
{
fileList.Content.ClearChildren();
fileList.BarScroll = 0.0f;
try
{
var directories = Directory.EnumerateDirectories(currentDirectory, "*" + filterBox.Text + "*");
foreach (var directory in directories)
{
string txt = directory;
if (txt.StartsWith(currentDirectory)) { txt = txt.Substring(currentDirectory.Length); }
if (!txt.EndsWith("/")) { txt += "/"; }
var itemFrame = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), fileList.Content.RectTransform), txt)
{
UserData = (bool?)true
};
var folderIcon = new GUIImage(new RectTransform(new Point((int)(itemFrame.Rect.Height * 0.8f)), itemFrame.RectTransform, Anchor.CenterLeft)
{
AbsoluteOffset = new Point((int)(itemFrame.Rect.Height * 0.25f), 0)
}, style: "OpenButton", scaleToFit: true);
itemFrame.Padding = new Vector4(folderIcon.Rect.Width * 1.5f, itemFrame.Padding.Y, itemFrame.Padding.Z, itemFrame.Padding.W);
}
IEnumerable<string> files = null;
foreach (string pattern in currentFileTypePattern.Split(','))
{
string patternTrimmed = pattern.Trim();
patternTrimmed = "*" + filterBox.Text + "*" + patternTrimmed;
if (files == null)
{
files = Directory.EnumerateFiles(currentDirectory, patternTrimmed);
}
else
{
files = files.Concat(Directory.EnumerateFiles(currentDirectory, patternTrimmed));
}
}
foreach (var file in files)
{
string txt = file;
if (txt.StartsWith(currentDirectory)) { txt = txt.Substring(currentDirectory.Length); }
var itemFrame = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), fileList.Content.RectTransform), txt)
{
UserData = (bool?)false
};
}
}
catch (Exception e)
{
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), fileList.Content.RectTransform), "Could not list items in directory: " + e.Message)
{
CanBeFocused = false
};
}
directoryBox.Text = currentDirectory;
fileBox.Text = "";
fileList.Deselect();
}
public static bool MoveToParentDirectory(GUIButton button, object userdata)
{
string dir = CurrentDirectory;
if (dir.EndsWith("/")) { dir = dir.Substring(0, dir.Length - 1); }
int index = dir.LastIndexOf("/");
if (index < 0) { return false; }
CurrentDirectory = CurrentDirectory.Substring(0, index+1);
return false;
}
public static void AddToGUIUpdateList()
{
if (!Open) { return; }
backgroundFrame?.AddToGUIUpdateList();
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,256 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace Barotrauma
{
public class GUIButton : GUIComponent
{
protected GUITextBlock textBlock;
public GUITextBlock TextBlock { get { return textBlock; } }
protected GUIFrame frame;
public GUIFrame Frame { get { return frame; } }
public delegate bool OnClickedHandler(GUIButton button, object obj);
public OnClickedHandler OnClicked;
public delegate bool OnPressedHandler();
public OnPressedHandler OnPressed;
public delegate bool OnButtonDownHandler();
public OnButtonDownHandler OnButtonDown;
public bool CanBeSelected = true;
public override bool Enabled
{
get
{
return enabled;
}
set
{
if (value == enabled) { return; }
enabled = frame.Enabled = textBlock.Enabled = value;
}
}
public override Color Color
{
get { return base.Color; }
set
{
base.Color = value;
frame.Color = value;
}
}
public override Color HoverColor
{
get { return base.HoverColor; }
set
{
base.HoverColor = value;
frame.HoverColor = value;
}
}
public override Color SelectedColor
{
get
{
return base.SelectedColor;
}
set
{
base.SelectedColor = value;
frame.SelectedColor = value;
}
}
public override Color PressedColor
{
get
{
return base.PressedColor;
}
set
{
base.PressedColor = value;
frame.PressedColor = value;
}
}
public override Color OutlineColor
{
get { return base.OutlineColor; }
set
{
base.OutlineColor = value;
if (frame != null) frame.OutlineColor = value;
}
}
public Color TextColor
{
get { return textBlock.TextColor; }
set { textBlock.TextColor = value; }
}
public Color HoverTextColor
{
get { return textBlock.HoverTextColor; }
set { textBlock.HoverTextColor = value; }
}
public override float FlashTimer
{
get { return Frame.FlashTimer; }
}
public override ScalableFont Font
{
get
{
return (textBlock == null) ? GUI.Font : textBlock.Font;
}
set
{
base.Font = value;
if (textBlock != null) textBlock.Font = value;
}
}
public string Text
{
get { return textBlock.Text; }
set { textBlock.Text = value; }
}
public bool ForceUpperCase
{
get { return textBlock.ForceUpperCase; }
set { textBlock.ForceUpperCase = value; }
}
public override string ToolTip
{
get
{
return base.ToolTip;
}
set
{
base.ToolTip = value;
textBlock.ToolTip = value;
}
}
public GUIButton(RectTransform rectT, string text = "", Alignment textAlignment = Alignment.Center, string style = "", Color? color = null) : base(style, rectT)
{
CanBeFocused = true;
HoverCursor = CursorState.Hand;
frame = new GUIFrame(new RectTransform(Vector2.One, rectT), style) { CanBeFocused = false };
if (style != null) { GUI.Style.Apply(frame, style == "" ? "GUIButton" : style); }
if (color.HasValue)
{
this.color = frame.Color = color.Value;
}
textBlock = new GUITextBlock(new RectTransform(Vector2.One, rectT, Anchor.Center), text, textAlignment: textAlignment, style: null)
{
TextColor = this.style == null ? Color.Black : this.style.TextColor,
HoverTextColor = this.style == null ? Color.Black : this.style.HoverTextColor,
SelectedTextColor = this.style == null ? Color.Black : this.style.SelectedTextColor,
CanBeFocused = false
};
if (rectT.Rect.Height == 0 && !string.IsNullOrEmpty(text))
{
RectTransform.Resize(new Point(RectTransform.Rect.Width, (int)Font.MeasureString(textBlock.Text).Y));
RectTransform.MinSize = textBlock.RectTransform.MinSize = new Point(0, System.Math.Max(rectT.MinSize.Y, Rect.Height));
TextBlock.SetTextPos();
}
GUI.Style.Apply(textBlock, "", this);
//if the text is in chinese/korean/japanese and we're not using a CJK-compatible font,
//use the default CJK font as a fallback
if (TextManager.IsCJK(textBlock.Text) && !textBlock.Font.IsCJK)
{
textBlock.Font = GUI.CJKFont;
}
Enabled = true;
}
public override void ApplyStyle(GUIComponentStyle style)
{
base.ApplyStyle(style);
if (frame != null) { frame.ApplyStyle(style); }
}
public override void Flash(Color? color = null, float flashDuration = 1.5f, bool useRectangleFlash = false, bool useCircularFlash = false, Vector2? flashRectInflate = null)
{
Frame.Flash(color, flashDuration, useRectangleFlash, useCircularFlash, flashRectInflate);
}
protected override void Draw(SpriteBatch spriteBatch)
{
//do nothing
}
protected override void Update(float deltaTime)
{
if (!Visible) return;
base.Update(deltaTime);
if (Rect.Contains(PlayerInput.MousePosition) && CanBeSelected && CanBeFocused && Enabled && GUI.IsMouseOn(this))
{
State = Selected ?
ComponentState.HoverSelected :
ComponentState.Hover;
if (PlayerInput.PrimaryMouseButtonDown())
{
OnButtonDown?.Invoke();
}
if (PlayerInput.PrimaryMouseButtonHeld())
{
if (OnPressed != null)
{
if (OnPressed())
{
State = ComponentState.Pressed;
}
}
else
{
State = ComponentState.Pressed;
}
}
else if (PlayerInput.PrimaryMouseButtonClicked())
{
GUI.PlayUISound(GUISoundType.Click);
if (OnClicked != null)
{
if (OnClicked(this, UserData))
{
State = ComponentState.Selected;
}
}
else
{
Selected = !Selected;
}
}
}
else
{
State = Selected ? ComponentState.Selected : ComponentState.None;
}
foreach (GUIComponent child in Children)
{
child.State = State;
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -13,8 +13,9 @@ namespace Barotrauma
public OnSelectedHandler OnSelected;
public OnSelectedHandler OnDropped;
private GUIButton button;
private GUIListBox listBox;
private readonly GUIButton button;
private readonly GUIImage icon;
private readonly GUIListBox listBox;
private RectTransform currentHighestParent;
private List<RectTransform> parentHierarchy = new List<RectTransform>();
@@ -41,7 +42,11 @@ namespace Barotrauma
public bool ButtonEnabled
{
get { return button.Enabled; }
set { button.Enabled = value; }
set
{
button.Enabled = value;
if (icon != null) { icon.Enabled = value; }
}
}
public GUIComponent SelectedComponent
@@ -49,6 +54,7 @@ namespace Barotrauma
get { return listBox.SelectedComponent; }
}
// TODO: fix implicit hiding
public bool Selected
{
get
@@ -83,6 +89,12 @@ namespace Barotrauma
}
}
public Color ButtonTextColor
{
get { return button.TextColor; }
set { button.TextColor = value; }
}
public void ReceiveTextInput(char inputChar)
{
GUI.KeyboardDispatcher.Subscriber = null;
@@ -139,8 +151,9 @@ namespace Barotrauma
}
}
public GUIDropDown(RectTransform rectT, string text = "", int elementCount = 4, string style = "", bool selectMultiple = false) : base(style, rectT)
public GUIDropDown(RectTransform rectT, string text = "", int elementCount = 4, string style = "", bool selectMultiple = false, bool dropAbove = false) : base(style, rectT)
{
HoverCursor = CursorState.Hand;
CanBeFocused = true;
this.selectMultiple = selectMultiple;
@@ -150,14 +163,25 @@ namespace Barotrauma
OnClicked = OnClicked
};
GUI.Style.Apply(button, "", this);
button.TextBlock.SetTextPos();
Anchor listAnchor = dropAbove ? Anchor.TopCenter : Anchor.BottomCenter;
Pivot listPivot = dropAbove ? Pivot.BottomCenter : Pivot.TopCenter;
listBox = new GUIListBox(new RectTransform(new Point(Rect.Width, Rect.Height * MathHelper.Clamp(elementCount, 2, 10)), rectT, listAnchor, listPivot)
listBox = new GUIListBox(new RectTransform(new Point(Rect.Width, Rect.Height * MathHelper.Clamp(elementCount, 2, 10)), rectT, Anchor.BottomCenter, Pivot.TopCenter)
{ IsFixedSize = false }, style: null)
{
Enabled = !selectMultiple,
OnSelected = SelectItem
};
GUI.Style.Apply(listBox.Content, "GUIListBox", this);
GUI.Style.Apply(listBox, "GUIListBox", this);
GUI.Style.Apply(listBox.ContentBackground, "GUIListBox", this);
if (button.Style.ChildStyles.ContainsKey("dropdownicon"))
{
icon = new GUIImage(new RectTransform(new Vector2(0.6f, 0.6f), button.RectTransform, Anchor.CenterRight, scaleBasis: ScaleBasis.BothHeight) { AbsoluteOffset = new Point(5, 0) }, null, scaleToFit: true);
icon.ApplyStyle(button.Style.ChildStyles["dropdownicon"]);
}
currentHighestParent = FindHighestParent();
currentHighestParent.GUIComponent.OnAddedToGUIUpdateList += AddListBoxToGUIUpdateList;
@@ -243,6 +267,7 @@ namespace Barotrauma
i++;
}
button.Text = string.Join(", ", texts);
// TODO: The callback is called at least twice, remove this?
OnSelected?.Invoke(tb.Parent, tb.Parent.UserData);
return true;
}
@@ -290,6 +315,7 @@ namespace Barotrauma
button.Text = textBlock.Text;
}
Dropped = false;
// TODO: OnSelected can be called multiple times and when it shouldn't be called -> turn into an event so that nobody else can call it.
OnSelected?.Invoke(component, component.UserData);
return true;
}
@@ -390,7 +416,7 @@ namespace Barotrauma
if (!Visible) return;
wasOpened = false;
base.Update(deltaTime);
if (Dropped && PlayerInput.LeftButtonClicked())
if (Dropped && PlayerInput.PrimaryMouseButtonClicked())
{
Rectangle listBoxRect = listBox.Rect;
if (!listBoxRect.Contains(PlayerInput.MousePosition) && !button.Rect.Contains(PlayerInput.MousePosition))

View File

@@ -8,6 +8,7 @@ namespace Barotrauma
{
public GUIFrame(RectTransform rectT, string style = "", Color? color = null) : base(style, rectT)
{
Enabled = true;
if (color.HasValue)
{
this.color = color.Value;
@@ -18,7 +19,7 @@ namespace Barotrauma
{
if (!Visible) return;
Color currColor = GetCurrentColor(state);
Color currColor = GetColor(State);
if (sprites == null || !sprites.Any(s => s.Value.Any())) GUI.DrawRectangle(spriteBatch, Rect, currColor * (currColor.A/255.0f), true);
base.Draw(spriteBatch);

View File

@@ -87,7 +87,7 @@ namespace Barotrauma
}
if (style == null)
{
color = hoverColor = selectedColor = pressedColor = Color.White;
color = hoverColor = selectedColor = pressedColor = disabledColor = Color.White;
}
if (!scaleToFit)
{
@@ -97,15 +97,17 @@ namespace Barotrauma
{
rectT.SizeChanged += RecalculateScale;
}
Enabled = true;
}
protected override void Draw(SpriteBatch spriteBatch)
{
if (!Visible) return;
if (Parent != null) { state = Parent.State; }
if (OverrideState != null) { state = OverrideState.Value; }
Color currColor = GetCurrentColor(state);
if (Parent != null) { State = Parent.State; }
if (OverrideState != null) { State = OverrideState.Value; }
Color currentColor = GetColor(State);
if (BlendState != null)
{
@@ -115,23 +117,23 @@ namespace Barotrauma
if (style != null)
{
foreach (UISprite uiSprite in style.Sprites[state])
foreach (UISprite uiSprite in style.Sprites[State])
{
if (Math.Abs(Rotation) > float.Epsilon)
{
float scale = Math.Min(Rect.Width / uiSprite.Sprite.size.X, Rect.Height / uiSprite.Sprite.size.Y);
spriteBatch.Draw(uiSprite.Sprite.Texture, Rect.Center.ToVector2(), uiSprite.Sprite.SourceRect, currColor * (currColor.A / 255.0f), Rotation, uiSprite.Sprite.size / 2,
spriteBatch.Draw(uiSprite.Sprite.Texture, Rect.Center.ToVector2(), uiSprite.Sprite.SourceRect, currentColor * (currentColor.A / 255.0f), Rotation, uiSprite.Sprite.size / 2,
Scale * scale, SpriteEffects, 0.0f);
}
else
{
uiSprite.Draw(spriteBatch, Rect, currColor * (currColor.A / 255.0f), SpriteEffects);
uiSprite.Draw(spriteBatch, Rect, currentColor * (currentColor.A / 255.0f), SpriteEffects);
}
}
}
else if (sprite?.Texture != null)
{
spriteBatch.Draw(sprite.Texture, Rect.Center.ToVector2(), sourceRect, currColor * (currColor.A / 255.0f), Rotation, sprite.size / 2,
spriteBatch.Draw(sprite.Texture, Rect.Center.ToVector2(), sourceRect, currentColor * (currentColor.A / 255.0f), Rotation, sprite.size / 2,
Scale, SpriteEffects, 0.0f);
}

View File

@@ -0,0 +1,197 @@
using Microsoft.Xna.Framework;
using System;
using System.Linq;
namespace Barotrauma
{
public class GUILayoutGroup : GUIComponent
{
private bool isHorizontal;
public bool IsHorizontal
{
get { return isHorizontal; }
set
{
isHorizontal = value;
needsToRecalculate = true;
}
}
private bool stretch;
/// <summary>
/// Note that stretching cannot be undone, because the previous child sizes are not stored.
/// </summary>
public bool Stretch
{
get { return stretch; }
set
{
stretch = value;
needsToRecalculate = true;
}
}
private int absoluteSpacing;
public int AbsoluteSpacing
{
get { return absoluteSpacing; }
set
{
absoluteSpacing = MathHelper.Clamp(value, 0, int.MaxValue);
needsToRecalculate = true;
}
}
private float relativeSpacing;
public float RelativeSpacing
{
get { return relativeSpacing; }
set
{
relativeSpacing = MathHelper.Clamp(value, -1, 1);
needsToRecalculate = true;
}
}
private Anchor childAnchor;
public Anchor ChildAnchor
{
get { return childAnchor; }
set
{
childAnchor = value;
needsToRecalculate = true;
}
}
private bool needsToRecalculate;
public bool NeedsToRecalculate
{
get { return needsToRecalculate; }
set
{
if (value) { needsToRecalculate = true; }
}
}
public GUILayoutGroup(RectTransform rectT, bool isHorizontal = false, Anchor childAnchor = Anchor.TopLeft) : base(null, rectT)
{
CanBeFocused = false;
this.isHorizontal = isHorizontal;
this.childAnchor = childAnchor;
rectT.ChildrenChanged += (child) => needsToRecalculate = true;
rectT.ScaleChanged += () => needsToRecalculate = true;
rectT.SizeChanged += () => needsToRecalculate = true;
}
public void Recalculate()
{
float stretchFactor = 1.0f;
if (stretch && RectTransform.Children.Count() > 0)
{
foreach (RectTransform child in RectTransform.Children)
{
if (child.GUIComponent.IgnoreLayoutGroups) { continue; }
if (child.ScaleBasis == ScaleBasis.BothHeight) { child.MinSize = new Point(child.Rect.Height, child.MinSize.Y); }
if (child.ScaleBasis == ScaleBasis.BothWidth) { child.MinSize = new Point(child.MinSize.X, child.Rect.Width); }
if (child.ScaleBasis == ScaleBasis.Smallest)
{
if (Rect.Width < Rect.Height)
{
child.MinSize = new Point(child.MinSize.X, child.Rect.Width);
}
else
{
child.MinSize = new Point(child.Rect.Height, child.MinSize.Y);
}
}
if (child.ScaleBasis == ScaleBasis.Largest)
{
if (Rect.Width > Rect.Height)
{
child.MinSize = new Point(child.MinSize.X, child.Rect.Width);
}
else
{
child.MinSize = new Point(child.Rect.Height, child.MinSize.Y);
}
}
}
float minSize = RectTransform.Children
.Where(c => !c.GUIComponent.IgnoreLayoutGroups)
.Sum(c => isHorizontal ? (c.IsFixedSize ? c.NonScaledSize.X : c.MinSize.X) : (c.IsFixedSize ? c.NonScaledSize.Y : c.MinSize.Y));
float totalSize = RectTransform.Children
.Where(c => !c.GUIComponent.IgnoreLayoutGroups)
.Sum(c => isHorizontal ?
(c.IsFixedSize ? c.Rect.Width : MathHelper.Clamp(c.Rect.Width, c.MinSize.X, c.MaxSize.X)) :
(c.IsFixedSize ? c.Rect.Height : MathHelper.Clamp(c.Rect.Height, c.MinSize.Y, c.MaxSize.Y)));
float thisSize = (isHorizontal ? Rect.Width : Rect.Height);
totalSize +=
(RectTransform.Children.Count(c => !c.GUIComponent.IgnoreLayoutGroups) - 1) *
(absoluteSpacing + relativeSpacing * thisSize);
stretchFactor = totalSize <= 0.0f || minSize >= thisSize ?
1.0f :
(thisSize - minSize) / (totalSize - minSize);
}
int absPos = 0;
float relPos = 0;
foreach (var child in RectTransform.Children)
{
if (child.GUIComponent.IgnoreLayoutGroups) { continue; }
child.SetPosition(childAnchor);
if (isHorizontal)
{
child.RelativeOffset = new Vector2(relPos, child.RelativeOffset.Y);
child.AbsoluteOffset = new Point(absPos, child.AbsoluteOffset.Y);
if (child.IsFixedSize)
{
absPos += child.NonScaledSize.X + absoluteSpacing;
}
else
{
absPos += (int)(MathHelper.Clamp(child.Rect.Width * stretchFactor, child.MinSize.X, child.MaxSize.X) + (absoluteSpacing * stretchFactor));
if (stretch)
{
child.RelativeSize = new Vector2(child.RelativeSize.X * stretchFactor, child.RelativeSize.Y);
}
}
}
else
{
child.RelativeOffset = new Vector2(child.RelativeOffset.X, relPos);
child.AbsoluteOffset = new Point(child.AbsoluteOffset.X, absPos);
if (child.IsFixedSize)
{
absPos += child.NonScaledSize.Y + absoluteSpacing;
}
else
{
absPos += (int)(MathHelper.Clamp(child.Rect.Height * stretchFactor, child.MinSize.Y, child.MaxSize.Y) + (absoluteSpacing * stretchFactor));
if (stretch)
{
child.RelativeSize = new Vector2(child.RelativeSize.X, child.RelativeSize.Y * stretchFactor);
}
}
}
relPos += relativeSpacing * stretchFactor;
}
needsToRecalculate = false;
}
protected override void Update(float deltaTime)
{
base.Update(deltaTime);
if (needsToRecalculate)
{
Recalculate();
}
}
}
}

View File

@@ -0,0 +1,818 @@
using EventInput;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Linq;
using Barotrauma.Extensions;
using Microsoft.Xna.Framework.Input;
namespace Barotrauma
{
public class GUIListBox : GUIComponent, IKeyboardSubscriber
{
protected List<GUIComponent> selected;
public delegate bool OnSelectedHandler(GUIComponent component, object obj);
public OnSelectedHandler OnSelected;
public delegate object CheckSelectedHandler();
public CheckSelectedHandler CheckSelected;
public delegate void OnRearrangedHandler(GUIListBox listBox, object obj);
public OnRearrangedHandler OnRearranged;
/// <summary>
/// A frame drawn behind the content of the listbox
/// </summary>
public GUIFrame ContentBackground { get; private set; }
/// <summary>
/// A frame that contains the contents of the listbox. The frame itself is not rendered.
/// </summary>
public GUIFrame Content { get; private set; }
public GUIScrollBar ScrollBar { get; private set; }
private Dictionary<GUIComponent, bool> childVisible = new Dictionary<GUIComponent, bool>();
private int totalSize;
private bool childrenNeedsRecalculation;
private bool scrollBarNeedsRecalculation;
private bool dimensionsNeedsRecalculation;
// TODO: Define in styles?
private int ScrollBarSize
{
get
{
//use the average of the "desired" size and the scaled size
//scaling the bar linearly with the resolution tends to make them too large on large resolutions
float desiredSize = 25.0f;
float scaledSize = desiredSize * GUI.Scale;
return (int)((desiredSize + scaledSize) / 2.0f);
}
}
public bool SelectMultiple;
public bool HideChildrenOutsideFrame = true;
private bool useGridLayout;
public bool UseGridLayout
{
get { return useGridLayout; }
set
{
if (useGridLayout == value) return;
useGridLayout = value;
childrenNeedsRecalculation = true;
scrollBarNeedsRecalculation = true;
}
}
private Vector4? overridePadding;
public Vector4 Padding
{
get
{
if (overridePadding.HasValue) { return overridePadding.Value; }
if (Style == null) { return Vector4.Zero; }
return Style.Padding;
}
set { overridePadding = value; }
}
public GUIComponent SelectedComponent
{
get
{
return selected.FirstOrDefault();
}
}
// TODO: fix implicit hiding
public bool Selected { get; set; }
public List<GUIComponent> AllSelected
{
get { return selected; }
}
public object SelectedData
{
get
{
return SelectedComponent?.UserData;
}
}
public int SelectedIndex
{
get
{
if (SelectedComponent == null) return -1;
return Content.RectTransform.GetChildIndex(SelectedComponent.RectTransform);
}
}
public float BarScroll
{
get { return ScrollBar.BarScroll; }
set { ScrollBar.BarScroll = value; }
}
public float BarSize
{
get { return ScrollBar.BarSize; }
}
public float TotalSize
{
get { return totalSize; }
}
public int Spacing { get; set; }
public override Color Color
{
get
{
return base.Color;
}
set
{
base.Color = value;
Content.Color = value;
}
}
/// <summary>
/// Disables the scroll bar without hiding it.
/// </summary>
public bool ScrollBarEnabled { get; set; } = true;
public bool KeepSpaceForScrollBar { get; set; }
public bool ScrollBarVisible
{
get
{
return ScrollBar.Visible;
}
set
{
if (ScrollBar.Visible == value) { return; }
ScrollBar.Visible = value;
dimensionsNeedsRecalculation = true;
}
}
/// <summary>
/// Automatically hides the scroll bar when the content fits in.
/// </summary>
public bool AutoHideScrollBar { get; set; } = true;
private bool IsScrollBarOnDefaultSide { get; set; }
public bool CanDragElements { get; set; } = false;
private GUIComponent draggedElement;
private Rectangle draggedReferenceRectangle;
private Point draggedReferenceOffset;
public GUIComponent DraggedElement => draggedElement;
/// <param name="isScrollBarOnDefaultSide">For horizontal listbox, default side is on the bottom. For vertical, it's on the right.</param>
public GUIListBox(RectTransform rectT, bool isHorizontal = false, Color? color = null, string style = "", bool isScrollBarOnDefaultSide = true) : base(style, rectT)
{
CanBeFocused = true;
selected = new List<GUIComponent>();
ContentBackground = new GUIFrame(new RectTransform(Vector2.One, rectT), style)
{
CanBeFocused = false
};
Content = new GUIFrame(new RectTransform(Vector2.One, ContentBackground.RectTransform), style: null)
{
CanBeFocused = false
};
Content.RectTransform.ChildrenChanged += (_) =>
{
scrollBarNeedsRecalculation = true;
childrenNeedsRecalculation = true;
};
if (style != null)
{
GUI.Style.Apply(ContentBackground, "", this);
}
if (color.HasValue)
{
this.color = color.Value;
}
IsScrollBarOnDefaultSide = isScrollBarOnDefaultSide;
Point size;
Anchor anchor;
if (isHorizontal)
{
size = new Point((int)(Rect.Width - Padding.X - Padding.Z), (int)(ScrollBarSize * GUI.Scale));
anchor = isScrollBarOnDefaultSide ? Anchor.BottomCenter : Anchor.TopCenter;
}
else
{
// TODO: Should this be multiplied by the GUI.Scale as well?
size = new Point(ScrollBarSize, (int)(Rect.Height - Padding.Y - Padding.W));
anchor = isScrollBarOnDefaultSide ? Anchor.CenterRight : Anchor.CenterLeft;
}
ScrollBar = new GUIScrollBar(
new RectTransform(size, rectT, anchor)
{
AbsoluteOffset = isHorizontal ?
new Point(0, IsScrollBarOnDefaultSide ? (int)Padding.W : (int)Padding.Y) :
new Point(IsScrollBarOnDefaultSide ? (int)Padding.Z : (int)Padding.X, 0)
},
isHorizontal: isHorizontal);
UpdateScrollBarSize();
Enabled = true;
ScrollBar.BarScroll = 0.0f;
RectTransform.ScaleChanged += () => dimensionsNeedsRecalculation = true;
RectTransform.SizeChanged += () => dimensionsNeedsRecalculation = true;
UpdateDimensions();
}
private void UpdateDimensions()
{
dimensionsNeedsRecalculation = false;
ContentBackground.RectTransform.Resize(Rect.Size);
bool reduceScrollbarSize = KeepSpaceForScrollBar ? ScrollBarEnabled : ScrollBarVisible;
Point contentSize = reduceScrollbarSize ? CalculateFrameSize(ScrollBar.IsHorizontal, ScrollBarSize) : Rect.Size;
Content.RectTransform.Resize(new Point((int)(contentSize.X - Padding.X - Padding.Z), (int)(contentSize.Y - Padding.Y - Padding.W)));
if (!IsScrollBarOnDefaultSide) { Content.RectTransform.SetPosition(Anchor.BottomRight); }
Content.RectTransform.AbsoluteOffset = new Point(
IsScrollBarOnDefaultSide ? (int)Padding.X : (int)Padding.Z,
IsScrollBarOnDefaultSide ? (int)Padding.Y : (int)Padding.W);
ScrollBar.RectTransform.Resize(ScrollBar.IsHorizontal ?
new Point((int)(Rect.Width - Padding.X - Padding.Z), ScrollBarSize) :
new Point(ScrollBarSize, (int)(Rect.Height - Padding.Y - Padding.W)));
ScrollBar.RectTransform.AbsoluteOffset = ScrollBar.IsHorizontal ?
new Point(0, (int)Padding.W) :
new Point((int)Padding.Z, 0);
UpdateScrollBarSize();
}
public void Select(object userData, bool force = false, bool autoScroll = true)
{
var children = Content.Children;
int i = 0;
foreach (GUIComponent child in children)
{
if ((child.UserData != null && child.UserData.Equals(userData)) ||
(child.UserData == null && userData == null))
{
Select(i, force, autoScroll);
if (!SelectMultiple) return;
}
i++;
}
}
private Point CalculateFrameSize(bool isHorizontal, int scrollBarSize)
=> isHorizontal ? new Point(Rect.Width, Rect.Height - scrollBarSize) : new Point(Rect.Width - scrollBarSize, Rect.Height);
private void RepositionChildren()
{
int x = 0, y = 0;
if (ScrollBar.BarSize < 1.0f)
{
if (ScrollBar.IsHorizontal)
{
x -= (int)((totalSize - Content.Rect.Width) * ScrollBar.BarScroll);
}
else
{
y -= (int)((totalSize - Content.Rect.Height) * ScrollBar.BarScroll);
}
}
for (int i = 0; i < Content.CountChildren; i++)
{
GUIComponent child = Content.GetChild(i);
if (!child.Visible) { continue; }
if (RectTransform != null)
{
if (child != draggedElement && (child.RectTransform.AbsoluteOffset.X != x || child.RectTransform.AbsoluteOffset.Y != y))
{
child.RectTransform.AbsoluteOffset = new Point(x, y);
}
}
if (useGridLayout)
{
if (ScrollBar.IsHorizontal)
{
if (y + child.Rect.Height + Spacing > Content.Rect.Height)
{
y = 0;
x += child.Rect.Width + Spacing;
if (child != draggedElement && (child.RectTransform.AbsoluteOffset.X != x || child.RectTransform.AbsoluteOffset.Y != y))
{
child.RectTransform.AbsoluteOffset = new Point(x, y);
}
y += child.Rect.Height + Spacing;
}
else
{
y += child.Rect.Height + Spacing;
}
}
else
{
if (x + child.Rect.Width + Spacing > Content.Rect.Width)
{
x = 0;
y += child.Rect.Height + Spacing;
if (child != draggedElement && (child.RectTransform.AbsoluteOffset.X != x || child.RectTransform.AbsoluteOffset.Y != y))
{
child.RectTransform.AbsoluteOffset = new Point(x, y);
}
x += child.Rect.Width + Spacing;
}
else
{
x += child.Rect.Width + Spacing;
}
}
}
else
{
if (ScrollBar.IsHorizontal)
{
x += child.Rect.Width + Spacing;
}
else
{
y += child.Rect.Height + Spacing;
}
}
}
}
private void UpdateChildrenRect()
{
//dragging
if (CanDragElements && draggedElement != null)
{
if (!PlayerInput.LeftButtonHeld())
{
OnRearranged?.Invoke(this, draggedElement.UserData);
draggedElement = null;
RepositionChildren();
}
else
{
draggedElement.RectTransform.AbsoluteOffset = draggedReferenceOffset + new Point(0, (int)PlayerInput.MousePosition.Y - draggedReferenceRectangle.Center.Y);
int index = Content.RectTransform.GetChildIndex(draggedElement.RectTransform);
int currIndex = index;
while (currIndex > 0 && PlayerInput.MousePosition.Y < draggedReferenceRectangle.Top)
{
currIndex--;
draggedReferenceRectangle.Y -= draggedReferenceRectangle.Height;
draggedReferenceOffset.Y -= draggedReferenceRectangle.Height;
}
while (currIndex < Content.CountChildren - 1 && PlayerInput.MousePosition.Y > draggedReferenceRectangle.Bottom)
{
currIndex++;
draggedReferenceRectangle.Y += draggedReferenceRectangle.Height;
draggedReferenceOffset.Y += draggedReferenceRectangle.Height;
}
if (currIndex != index)
{
draggedElement.RectTransform.RepositionChildInHierarchy(currIndex);
}
return;
}
}
for (int i = 0; i < Content.CountChildren; i++)
{
var child = Content.RectTransform.GetChild(i)?.GUIComponent;
if (child == null) continue;
// selecting
if (Enabled && CanBeFocused && child.CanBeFocused && (GUI.IsMouseOn(child)) && child.Rect.Contains(PlayerInput.MousePosition))
{
child.State = ComponentState.Hover;
if (PlayerInput.PrimaryMouseButtonClicked())
{
Select(i, autoScroll: false);
}
if (CanDragElements && PlayerInput.LeftButtonDown() && GUI.MouseOn == child)
{
draggedElement = child;
draggedReferenceRectangle = child.Rect;
draggedReferenceOffset = child.RectTransform.AbsoluteOffset;
}
}
else if (selected.Contains(child))
{
child.State = ComponentState.Selected;
if (CheckSelected != null)
{
if (CheckSelected() != child.UserData) selected.Remove(child);
}
}
else
{
child.State = ComponentState.None;
}
}
}
public override void AddToGUIUpdateList(bool ignoreChildren = false, int order = 0)
{
if (!Visible) { return; }
if (!ignoreChildren)
{
foreach (GUIComponent child in Children)
{
if (child == Content || child == ScrollBar || child == ContentBackground) { continue; }
child.AddToGUIUpdateList(ignoreChildren, order);
}
}
foreach (GUIComponent child in Content.Children)
{
if (!childVisible.ContainsKey(child)) { childVisible[child] = child.Visible; }
if (childVisible[child] != child.Visible)
{
childVisible[child] = child.Visible;
childrenNeedsRecalculation = true;
scrollBarNeedsRecalculation = true;
break;
}
}
if (childrenNeedsRecalculation)
{
RecalculateChildren();
childVisible.Clear();
}
UpdateOrder = order;
GUI.AddToUpdateList(this);
if (ignoreChildren)
{
OnAddedToGUIUpdateList?.Invoke(this);
return;
}
int lastVisible = 0;
for (int i = 0; i < Content.CountChildren; i++)
{
var child = Content.GetChild(i);
if (!child.Visible) continue;
if (!IsChildInsideFrame(child))
{
if (lastVisible > 0) break;
continue;
}
lastVisible = i;
child.AddToGUIUpdateList(false, order);
}
if (ScrollBar.Enabled)
{
ScrollBar.AddToGUIUpdateList(false, order);
}
OnAddedToGUIUpdateList?.Invoke(this);
}
public void RecalculateChildren()
{
foreach (GUIComponent child in Content.Children)
{
ClampChildMouseRects(child);
}
RepositionChildren();
childrenNeedsRecalculation = false;
}
private void ClampChildMouseRects(GUIComponent child)
{
child.ClampMouseRectToParent = true;
//no need to go through grandchildren if the child is a GUIListBox, it handles this by itself
if (child is GUIListBox) { return; }
foreach (GUIComponent grandChild in child.Children)
{
ClampChildMouseRects(grandChild);
}
}
protected override void Update(float deltaTime)
{
if (!Visible) { return; }
UpdateChildrenRect();
RepositionChildren();
if (scrollBarNeedsRecalculation)
{
UpdateScrollBarSize();
}
if ((GUI.IsMouseOn(this) || GUI.IsMouseOn(ScrollBar)) && PlayerInput.ScrollWheelSpeed != 0)
{
ScrollBar.BarScroll -= (PlayerInput.ScrollWheelSpeed / 500.0f) * BarSize;
}
ScrollBar.Enabled = ScrollBarEnabled && BarSize < 1.0f;
if (AutoHideScrollBar)
{
ScrollBarVisible = ScrollBar.Enabled;
}
if (dimensionsNeedsRecalculation)
{
UpdateDimensions();
}
}
public void SelectNext(bool force = false, bool autoScroll = true)
{
int index = SelectedIndex + 1;
while (index < Content.CountChildren)
{
if (Content.GetChild(index).Visible)
{
Select(index, force, autoScroll);
break;
}
index++;
}
}
public void SelectPrevious(bool force = false, bool autoScroll = true)
{
int index = SelectedIndex - 1;
while (index >= 0)
{
if (Content.GetChild(index).Visible)
{
Select(index, force, autoScroll);
break;
}
index--;
}
}
public void Select(int childIndex, bool force = false, bool autoScroll = true)
{
if (childIndex >= Content.CountChildren || childIndex < 0) { return; }
GUIComponent child = Content.GetChild(childIndex);
bool wasSelected = true;
if (OnSelected != null)
{
// TODO: The callback is called twice, fix this!
wasSelected = force || OnSelected(child, child.UserData);
}
if (!wasSelected) { return; }
if (SelectMultiple)
{
if (selected.Contains(child))
{
selected.Remove(child);
}
else
{
selected.Add(child);
}
}
else
{
selected.Clear();
selected.Add(child);
}
// Ensure that the selected element is visible. This may not be the case, if the selection is run from code. (e.g. if we have two list boxes that are synced)
// TODO: This method only works when moving one item up/down (e.g. when using the up and down arrows)
if (autoScroll)
{
if (ScrollBar.IsHorizontal)
{
if (child.Rect.X < MouseRect.X)
{
//child outside the left edge of the frame -> move left
ScrollBar.BarScroll -= (float)(MouseRect.X - child.Rect.X) / (totalSize - Content.Rect.Width);
}
else if (child.Rect.Right > MouseRect.Right)
{
//child outside the right edge of the frame -> move right
ScrollBar.BarScroll += (float)(child.Rect.Right - MouseRect.Right) / (totalSize - Content.Rect.Width);
}
}
else
{
if (child.Rect.Y < MouseRect.Y)
{
//child above the top of the frame -> move up
ScrollBar.BarScroll -= (float)(MouseRect.Y - child.Rect.Y) / (totalSize - Content.Rect.Height);
}
else if (child.Rect.Bottom > MouseRect.Bottom)
{
//child below the bottom of the frame -> move down
ScrollBar.BarScroll += (float)(child.Rect.Bottom - MouseRect.Bottom) / (totalSize - Content.Rect.Height);
}
}
}
// If one of the children is the subscriber, we don't want to register, because it will unregister the child.
if (RectTransform.GetAllChildren().None(rt => rt.GUIComponent == GUI.KeyboardDispatcher.Subscriber))
{
Selected = true;
GUI.KeyboardDispatcher.Subscriber = this;
}
}
public void Deselect()
{
Selected = false;
if (GUI.KeyboardDispatcher.Subscriber == this)
{
GUI.KeyboardDispatcher.Subscriber = null;
}
selected.Clear();
}
public void UpdateScrollBarSize()
{
scrollBarNeedsRecalculation = false;
if (Content == null) { return; }
totalSize = 0;
var children = Content.Children.Where(c => c.Visible);
if (useGridLayout)
{
int pos = 0;
foreach (GUIComponent child in children)
{
if (ScrollBar.IsHorizontal)
{
if (pos + child.Rect.Height + Spacing > Content.Rect.Height)
{
pos = 0;
totalSize += child.Rect.Width + Spacing;
}
pos += child.Rect.Height + Spacing;
if (child == children.Last())
{
totalSize += child.Rect.Width + Spacing;
}
}
else
{
if (pos + child.Rect.Width + Spacing > Content.Rect.Width)
{
pos = 0;
totalSize += child.Rect.Height + Spacing;
}
pos += child.Rect.Width + Spacing;
if (child == children.Last())
{
totalSize += child.Rect.Height + Spacing;
}
}
}
}
else
{
foreach (GUIComponent child in children)
{
totalSize += (ScrollBar.IsHorizontal) ? child.Rect.Width : child.Rect.Height;
}
totalSize += Content.CountChildren * Spacing;
}
float minScrollBarSize = 20.0f;
ScrollBar.BarSize = ScrollBar.IsHorizontal ?
Math.Max(Math.Min(Content.Rect.Width / (float)totalSize, 1.0f), minScrollBarSize / Content.Rect.Width) :
Math.Max(Math.Min(Content.Rect.Height / (float)totalSize, 1.0f), minScrollBarSize / Content.Rect.Height);
}
public override void ClearChildren()
{
Content.ClearChildren();
selected.Clear();
}
public override void RemoveChild(GUIComponent child)
{
if (child == null) { return; }
child.RectTransform.Parent = null;
if (selected.Contains(child)) selected.Remove(child);
UpdateScrollBarSize();
}
public override void DrawChildren(SpriteBatch spriteBatch, bool recursive)
{
//do nothing (the children have to be drawn in the Draw method after the ScissorRectangle has been set)
return;
}
protected override void Draw(SpriteBatch spriteBatch)
{
if (!Visible) { return; }
ContentBackground.DrawManually(spriteBatch, alsoChildren: false);
Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle;
RasterizerState prevRasterizerState = spriteBatch.GraphicsDevice.RasterizerState;
if (HideChildrenOutsideFrame)
{
spriteBatch.End();
spriteBatch.GraphicsDevice.ScissorRectangle = Rectangle.Intersect(prevScissorRect, Content.Rect);
spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable);
}
var children = Content.Children;
int lastVisible = 0;
int i = 0;
foreach (GUIComponent child in Content.Children)
{
if (!child.Visible) continue;
if (!IsChildInsideFrame(child))
{
if (lastVisible > 0) break;
continue;
}
lastVisible = i;
child.DrawManually(spriteBatch, alsoChildren: true, recursive: true);
i++;
}
if (HideChildrenOutsideFrame)
{
spriteBatch.End();
spriteBatch.GraphicsDevice.ScissorRectangle = prevScissorRect;
spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: prevRasterizerState);
}
if (ScrollBarVisible)
{
ScrollBar.DrawManually(spriteBatch, alsoChildren: true, recursive: true);
}
}
private bool IsChildInsideFrame(GUIComponent child)
{
if (child == null) { return false; }
if (ScrollBar.IsHorizontal)
{
if (child.Rect.Right < Content.Rect.X) { return false; }
if (child.Rect.X > Content.Rect.Right) { return false; }
}
else
{
if (child.Rect.Bottom < Content.Rect.Y) { return false; }
if (child.Rect.Y > Content.Rect.Bottom) { return false; }
}
return true;
}
public void ReceiveTextInput(char inputChar)
{
GUI.KeyboardDispatcher.Subscriber = null;
}
public void ReceiveTextInput(string text) { }
public void ReceiveCommandInput(char command) { }
public void ReceiveSpecialInput(Keys key)
{
switch (key)
{
case Keys.Down:
SelectNext();
break;
case Keys.Up:
SelectPrevious();
break;
case Keys.Enter:
case Keys.Space:
case Keys.Escape:
GUI.KeyboardDispatcher.Subscriber = null;
break;
}
}
}
}

View File

@@ -14,6 +14,9 @@ namespace Barotrauma
get { return Math.Max(400, 400 * (GameMain.GraphicsWidth / 1920)); }
}
private float inGameCloseTimer = 0.0f;
private const float inGameCloseTime = 15f;
public enum Type
{
Default,
@@ -87,7 +90,7 @@ namespace Barotrauma
Content = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.85f), InnerFrame.RectTransform, Anchor.Center)) { AbsoluteSpacing = 5 };
Header = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), Content.RectTransform),
headerText, textAlignment: Alignment.Center, wrap: true);
headerText, font: GUI.SubHeadingFont, textAlignment: Alignment.Center, wrap: true);
GUI.Style.Apply(Header, "", this);
Header.RectTransform.MinSize = new Point(0, Header.Rect.Height);
@@ -100,21 +103,28 @@ namespace Barotrauma
Text.RectTransform.IsFixedSize = true;
}
var buttonContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.15f), Content.RectTransform, Anchor.BottomCenter, maxSize: new Point(1000, 50)),
isHorizontal: true, childAnchor: buttons.Length > 1 ? Anchor.BottomLeft : Anchor.Center)
var buttonContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.15f), Content.RectTransform, Anchor.BottomCenter), childAnchor: Anchor.TopCenter)
{
AbsoluteSpacing = 5,
IgnoreLayoutGroups = true
};
int buttonSize = 35;
var buttonStyle = GUI.Style.GetComponentStyle("GUIButton");
if (buttonStyle != null && buttonStyle.Height.HasValue)
{
buttonSize = buttonStyle.Height.Value;
}
buttonContainer.RectTransform.NonScaledSize = buttonContainer.RectTransform.MinSize = buttonContainer.RectTransform.MaxSize =
new Point(buttonContainer.Rect.Width, (int)(30 * GUI.Scale));
new Point(buttonContainer.Rect.Width, (int)((buttonSize + 5) * buttons.Length));
buttonContainer.RectTransform.IsFixedSize = true;
if (height == 0)
{
height += Header.Rect.Height + Content.AbsoluteSpacing;
height += (Text == null ? 0 : Text.Rect.Height) + Content.AbsoluteSpacing;
height += buttonContainer.Rect.Height;
height += buttonContainer.Rect.Height + 20;
if (minSize.HasValue) { height = Math.Max(height, minSize.Value.Y); }
InnerFrame.RectTransform.NonScaledSize =
@@ -126,7 +136,7 @@ namespace Barotrauma
Buttons = new List<GUIButton>(buttons.Length);
for (int i = 0; i < buttons.Length; i++)
{
var button = new GUIButton(new RectTransform(new Vector2(Math.Min(0.9f / buttons.Length, 0.5f), 1.0f), buttonContainer.RectTransform), buttons[i], style: "GUIButtonLarge");
var button = new GUIButton(new RectTransform(new Vector2(0.6f, 1.0f / buttons.Length), buttonContainer.RectTransform), buttons[i]);
Buttons.Add(button);
}
}
@@ -154,7 +164,7 @@ namespace Barotrauma
Buttons = new List<GUIButton>(1)
{
new GUIButton(new RectTransform(new Vector2(0.5f, 0.5f), buttonContainer.RectTransform, Anchor.Center),
style: GUI.Style.GetComponentStyle("GUIButtonSolidHorizontalArrow") != null ? "GUIButtonSolidHorizontalArrow" : "GUIButtonHorizontalArrow")
style: "GUIButtonHorizontalArrow")
{
OnClicked = Close
}
@@ -168,10 +178,10 @@ namespace Barotrauma
{
Text = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), Content.RectTransform), text, textAlignment: textAlignment, wrap: true);
GUI.Style.Apply(Text, "", this);
/*Content.Recalculate();
Content.Recalculate();
Text.RectTransform.NonScaledSize = Text.RectTransform.MinSize = Text.RectTransform.MaxSize =
new Point(Text.Rect.Width, Text.Rect.Height);
Text.RectTransform.IsFixedSize = true;*/
Text.RectTransform.IsFixedSize = true;
}
if (height == 0)
@@ -238,6 +248,13 @@ namespace Barotrauma
{
InnerFrame.RectTransform.AbsoluteOffset = Vector2.SmoothStep(initialPos, defaultPos, openState).ToPoint();
openState = Math.Min(openState + deltaTime * 2.0f, 1.0f);
inGameCloseTimer += deltaTime;
if (inGameCloseTimer >= inGameCloseTime)
{
Close();
}
}
else
{

View File

@@ -0,0 +1,417 @@
using Microsoft.Xna.Framework;
using System;
using System.Globalization;
using System.Linq;
namespace Barotrauma
{
class GUINumberInput : GUIComponent
{
public enum NumberType
{
Int, Float
}
public delegate void OnValueChangedHandler(GUINumberInput numberInput);
public OnValueChangedHandler OnValueChanged;
public GUITextBox TextBox { get; private set; }
private GUIButton plusButton, minusButton;
private NumberType inputType;
public NumberType InputType
{
get { return inputType; }
set
{
if (inputType == value) { return; }
inputType = value;
if (inputType == NumberType.Int ||
(inputType == NumberType.Float && MinValueFloat > float.MinValue && MaxValueFloat < float.MaxValue))
{
ShowPlusMinusButtons();
}
else
{
HidePlusMinusButtons();
}
}
}
private float? minValueFloat, maxValueFloat;
public float? MinValueFloat
{
get { return minValueFloat; }
set
{
minValueFloat = value;
ClampFloatValue();
if (inputType == NumberType.Int ||
(inputType == NumberType.Float && MinValueFloat > float.MinValue && MaxValueFloat < float.MaxValue))
{
ShowPlusMinusButtons();
}
else
{
HidePlusMinusButtons();
}
}
}
public float? MaxValueFloat
{
get { return maxValueFloat; }
set
{
maxValueFloat = value;
ClampFloatValue();
if (inputType == NumberType.Int ||
(inputType == NumberType.Float && MinValueFloat > float.MinValue && MaxValueFloat < float.MaxValue))
{
ShowPlusMinusButtons();
}
else
{
HidePlusMinusButtons();
}
}
}
private float floatValue;
public float FloatValue
{
get { return floatValue; }
set
{
if (MathUtils.NearlyEqual(value, floatValue)) return;
floatValue = value;
ClampFloatValue();
float newValue = floatValue;
UpdateText();
//UpdateText may remove decimals from the value, force to full accuracy
floatValue = newValue;
OnValueChanged?.Invoke(this);
}
}
private int decimalsToDisplay = 1;
public int DecimalsToDisplay
{
get { return decimalsToDisplay; }
set
{
decimalsToDisplay = value;
UpdateText();
}
}
private int? minValueInt, maxValueInt;
public int? MinValueInt
{
get { return minValueInt; }
set
{
minValueInt = value;
ClampIntValue();
}
}
public int? MaxValueInt
{
get { return maxValueInt; }
set
{
maxValueInt = value;
ClampIntValue();
}
}
private int intValue;
public int IntValue
{
get { return intValue; }
set
{
if (value == intValue) return;
intValue = value;
UpdateText();
}
}
public override bool Enabled
{
get => base.Enabled;
set
{
plusButton.Enabled = true;
minusButton.Enabled = true;
if (InputType == NumberType.Int) { ClampIntValue(); } else { ClampFloatValue(); }
TextBox.Enabled = value;
if (!value)
{
plusButton.Enabled = false;
minusButton.Enabled = false;
}
}
}
public override ScalableFont Font
{
get
{
return base.Font;
}
set
{
base.Font = value;
if (TextBox != null) { TextBox.Font = value; }
}
}
public GUILayoutGroup LayoutGroup
{
get;
private set;
}
public float valueStep;
private float pressedTimer;
private float pressedDelay = 0.5f;
private bool IsPressedTimerRunning { get { return pressedTimer > 0; } }
public GUINumberInput(RectTransform rectT, NumberType inputType, string style = "", Alignment textAlignment = Alignment.Center, float? relativeButtonAreaWidth = null) : base(style, rectT)
{
LayoutGroup = new GUILayoutGroup(new RectTransform(Vector2.One, rectT), isHorizontal: true, childAnchor: Anchor.CenterLeft) { Stretch = true };
float _relativeButtonAreaWidth = relativeButtonAreaWidth ?? MathHelper.Clamp(Rect.Height / (float)Rect.Width, 0.1f, 0.25f);
TextBox = new GUITextBox(new RectTransform(new Vector2(1.0f - _relativeButtonAreaWidth, 1.0f), LayoutGroup.RectTransform), textAlignment: textAlignment, style: "GUITextBoxNoIcon")
{
ClampText = false
};
TextBox.CaretColor = TextBox.TextColor;
TextBox.OnTextChanged += TextChanged;
var buttonArea = new GUIFrame(new RectTransform(new Vector2(_relativeButtonAreaWidth, 1.0f), LayoutGroup.RectTransform, Anchor.CenterRight), style: null);
plusButton = new GUIButton(new RectTransform(new Vector2(1.0f, 0.5f), buttonArea.RectTransform), style: null);
GUI.Style.Apply(plusButton, "PlusButton", this);
plusButton.OnButtonDown += () =>
{
pressedTimer = pressedDelay;
return true;
};
plusButton.OnClicked += (button, data) =>
{
IncreaseValue();
return true;
};
plusButton.OnPressed += () =>
{
if (!IsPressedTimerRunning)
{
IncreaseValue();
}
return true;
};
minusButton = new GUIButton(new RectTransform(new Vector2(1.0f, 0.5f), buttonArea.RectTransform, Anchor.BottomRight), style: null);
GUI.Style.Apply(minusButton, "MinusButton", this);
minusButton.OnButtonDown += () =>
{
pressedTimer = pressedDelay;
return true;
};
minusButton.OnClicked += (button, data) =>
{
ReduceValue();
return true;
};
minusButton.OnPressed += () =>
{
if (!IsPressedTimerRunning)
{
ReduceValue();
}
return true;
};
if (inputType != NumberType.Int)
{
HidePlusMinusButtons();
}
if (inputType == NumberType.Int)
{
UpdateText();
TextBox.OnEnterPressed += (txtBox, txt) =>
{
UpdateText();
TextBox.Deselect();
return true;
};
TextBox.OnDeselected += (txtBox, key) => UpdateText();
}
else if (inputType == NumberType.Float)
{
UpdateText();
TextBox.OnDeselected += (txtBox, key) => UpdateText();
TextBox.OnEnterPressed += (txtBox, txt) =>
{
UpdateText();
TextBox.Deselect();
return true;
};
}
InputType = inputType;
switch (InputType)
{
case NumberType.Int:
TextBox.textFilterFunction = text => new string(text.Where(c => char.IsNumber(c) || c == '-').ToArray());
break;
case NumberType.Float:
TextBox.textFilterFunction = text => new string(text.Where(c => char.IsDigit(c) || c == '.' || c == '-').ToArray());
break;
}
RectTransform.MinSize = TextBox.RectTransform.MinSize;
LayoutGroup.Recalculate();
}
private void HidePlusMinusButtons()
{
plusButton.Parent.Visible = false;
plusButton.Parent.IgnoreLayoutGroups = true;
TextBox.RectTransform.RelativeSize = Vector2.One;
LayoutGroup.Recalculate();
}
private void ShowPlusMinusButtons()
{
plusButton.Parent.Visible = true;
plusButton.Parent.IgnoreLayoutGroups = false;
TextBox.RectTransform.RelativeSize = new Vector2(1.0f - plusButton.Parent.RectTransform.RelativeSize.X, 1.0f);
LayoutGroup.Recalculate();
}
private void ReduceValue()
{
if (inputType == NumberType.Int)
{
IntValue -= valueStep > 0 ? (int)valueStep : 1;
}
else if (maxValueFloat.HasValue && minValueFloat.HasValue)
{
FloatValue -= valueStep > 0 ? valueStep : Round();
}
}
private void IncreaseValue()
{
if (inputType == NumberType.Int)
{
IntValue += valueStep > 0 ? (int)valueStep : 1;
}
else if (inputType == NumberType.Float)
{
FloatValue += valueStep > 0 ? valueStep : Round();
}
}
/// <summary>
/// Calculates one percent between the range as the increment/decrement.
/// This value is rounded so that the bigger it is, the less decimals are used (min 0, max 3).
/// Return value is clamped between 0.1f and 1000.
/// </summary>
private float Round()
{
if (!maxValueFloat.HasValue || !minValueFloat.HasValue) return 0;
float onePercent = MathHelper.Lerp(minValueFloat.Value, maxValueFloat.Value, 0.01f);
float diff = maxValueFloat.Value - minValueFloat.Value;
int decimals = (int)MathHelper.Lerp(3, 0, MathUtils.InverseLerp(10, 1000, diff));
return MathHelper.Clamp((float)Math.Round(onePercent, decimals), 0.1f, 1000);
}
private bool TextChanged(GUITextBox textBox, string text)
{
switch (InputType)
{
case NumberType.Int:
int newIntValue = IntValue;
if (string.IsNullOrWhiteSpace(text) || text == "-")
{
intValue = 0;
}
else if (int.TryParse(text, out newIntValue))
{
intValue = newIntValue;
}
ClampIntValue();
break;
case NumberType.Float:
float newFloatValue = FloatValue;
if (string.IsNullOrWhiteSpace(text) || text == "-")
{
floatValue = 0;
}
else if (float.TryParse(text, NumberStyles.Any, CultureInfo.InvariantCulture, out newFloatValue))
{
floatValue = newFloatValue;
}
ClampFloatValue();
break;
}
OnValueChanged?.Invoke(this);
return true;
}
private void ClampFloatValue()
{
if (MinValueFloat != null)
{
floatValue = Math.Max(floatValue, MinValueFloat.Value);
minusButton.Enabled = floatValue > MinValueFloat;
}
if (MaxValueFloat != null)
{
floatValue = Math.Min(floatValue, MaxValueFloat.Value);
plusButton.Enabled = floatValue < MaxValueFloat;
}
}
private void ClampIntValue()
{
if (MinValueInt != null)
{
intValue = Math.Max(intValue, MinValueInt.Value);
minusButton.Enabled = intValue > MinValueInt;
}
if (MaxValueInt != null)
{
intValue = Math.Min(intValue, MaxValueInt.Value);
plusButton.Enabled = intValue < MaxValueInt;
}
}
private void UpdateText()
{
switch (InputType)
{
case NumberType.Float:
TextBox.Text = FloatValue.Format(decimalsToDisplay);
break;
case NumberType.Int:
TextBox.Text = IntValue.ToString();
break;
}
}
protected override void Update(float deltaTime)
{
base.Update(deltaTime);
if (IsPressedTimerRunning)
{
pressedTimer -= deltaTime;
}
}
}
}

View File

@@ -0,0 +1,168 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Linq;
namespace Barotrauma
{
public class GUIProgressBar : GUIComponent
{
private bool isHorizontal;
private readonly GUIFrame frame, slider;
private float barSize;
private readonly bool showFrame;
public delegate float ProgressGetterHandler();
public ProgressGetterHandler ProgressGetter;
public bool IsHorizontal
{
get { return isHorizontal; }
set { isHorizontal = value; }
}
public float BarSize
{
get { return barSize; }
set
{
if (!MathUtils.IsValid(value))
{
GameAnalyticsManager.AddErrorEventOnce(
"GUIProgressBar.BarSize_setter",
GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
"Attempted to set the BarSize of a GUIProgressBar to an invalid value (" + value + ")\n" + Environment.StackTrace);
return;
}
barSize = MathHelper.Clamp(value, 0.0f, 1.0f);
//UpdateRect();
}
}
public GUIProgressBar(RectTransform rectT, float barSize, Color? color = null, string style = "", bool showFrame = true) : base(style, rectT)
{
if (color.HasValue)
{
this.color = color.Value;
}
isHorizontal = (Rect.Width > Rect.Height);
frame = new GUIFrame(new RectTransform(Vector2.One, rectT));
GUI.Style.Apply(frame, "", this);
slider = new GUIFrame(new RectTransform(Vector2.One, rectT));
GUI.Style.Apply(slider, "Slider", this);
this.showFrame = showFrame;
this.barSize = barSize;
Enabled = true;
}
/// <summary>
/// Get the area the slider should be drawn inside
/// </summary>
/// <param name="fillAmount">0 = empty, 1 = full</param>
public Rectangle GetSliderRect(float fillAmount)
{
Rectangle sliderArea = new Rectangle(
frame.Rect.X + (int)style.Padding.X,
frame.Rect.Y + (int)style.Padding.Y,
(int)(frame.Rect.Width - style.Padding.X - style.Padding.Z),
(int)(frame.Rect.Height - style.Padding.Y - style.Padding.W));
Vector4 sliceBorderSizes = Vector4.Zero;
if (slider.sprites.ContainsKey(slider.State) && (slider.sprites[slider.State].First()?.Slice ?? false))
{
var slices = slider.sprites[slider.State].First().Slices;
sliceBorderSizes = new Vector4(slices[0].Width, slices[0].Height, slices[8].Width, slices[8].Height);
sliceBorderSizes *= slider.sprites[slider.State].First().GetSliceBorderScale(sliderArea.Size);
}
Rectangle sliderRect = IsHorizontal ?
new Rectangle(
sliderArea.X + (int)sliceBorderSizes.X,
sliderArea.Y,
(int)((sliderArea.Width - sliceBorderSizes.X - sliceBorderSizes.Z) * fillAmount),
sliderArea.Height)
:
new Rectangle(
sliderArea.X,
(int)(sliderArea.Bottom - (sliderArea.Height - sliceBorderSizes.Y - sliceBorderSizes.W) * fillAmount - sliceBorderSizes.W),
sliderArea.Width,
(int)((sliderArea.Height - sliceBorderSizes.Y - sliceBorderSizes.W) * fillAmount));
sliderRect.Width = Math.Max(sliderRect.Width, 1);
sliderRect.Height = Math.Max(sliderRect.Height, 1);
return sliderRect;
}
protected override void Draw(SpriteBatch spriteBatch)
{
if (!Visible) { return; }
if (ProgressGetter != null)
{
float newSize = MathHelper.Clamp(ProgressGetter(), 0.0f, 1.0f);
if (!MathUtils.IsValid(newSize))
{
GameAnalyticsManager.AddErrorEventOnce(
"GUIProgressBar.Draw:GetProgress",
GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
"ProgressGetter of a GUIProgressBar (" + ProgressGetter.Target.ToString() + " - " + ProgressGetter.Method.ToString() + ") returned an invalid value (" + newSize + ")\n" + Environment.StackTrace);
}
else
{
BarSize = newSize;
}
}
var sliderRect = GetSliderRect(barSize);
slider.RectTransform.AbsoluteOffset = new Point((int)style.Padding.X, (int)style.Padding.Y);
slider.RectTransform.MaxSize = new Point(
(int)(Rect.Width - style.Padding.X + style.Padding.Z),
(int)(Rect.Height - style.Padding.Y + style.Padding.W));
frame.Visible = showFrame;
slider.Visible = true;
if (showFrame)
{
if (AutoDraw)
{
frame.DrawAuto(spriteBatch);
}
else
{
frame.DrawManually(spriteBatch);
}
}
Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle;
if (BarSize <= 1.0f)
{
spriteBatch.End();
spriteBatch.GraphicsDevice.ScissorRectangle = Rectangle.Intersect(prevScissorRect, sliderRect);
spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable);
}
Color currColor = GetColor(State);
slider.Color = currColor;
if (AutoDraw)
{
slider.DrawAuto(spriteBatch);
}
else
{
slider.DrawManually(spriteBatch);
}
//hide the slider, we've already drawn it manually
frame.Visible = false;
slider.Visible = false;
if (BarSize <= 1.0f)
{
spriteBatch.End();
spriteBatch.Begin(SpriteSortMode.Deferred, rasterizerState: GameMain.ScissorTestEnable);
spriteBatch.GraphicsDevice.ScissorRectangle = prevScissorRect;
}
}
}
}

View File

@@ -54,8 +54,9 @@ namespace Barotrauma
}
}
private int? selected;
public int? Selected
// intentional hiding?
private new int? selected;
public new int? Selected
{
get
{
@@ -64,7 +65,7 @@ namespace Barotrauma
set
{
OnSelect?.Invoke(this, value);
if (selected != null && selected.Equals(value)) return;
if (selected != null && selected.Equals(value)) { return; }
selected = value;
foreach (KeyValuePair<int, GUITickBox> radioButton in radioButtons)
{
@@ -72,7 +73,10 @@ namespace Barotrauma
{
radioButton.Value.Selected = true;
}
else if (radioButton.Value.Selected) radioButton.Value.Selected = false;
else if (radioButton.Value.Selected)
{
radioButton.Value.Selected = false;
}
}
}
}

View File

@@ -7,7 +7,10 @@ namespace Barotrauma
{
public class GUIScrollBar : GUIComponent
{
public static GUIScrollBar draggingBar;
public static GUIScrollBar DraggingBar
{
get; private set;
}
private bool isHorizontal;
@@ -77,7 +80,11 @@ namespace Barotrauma
{
enabled = value;
Bar.Enabled = value;
if (!enabled) Bar.Selected = false;
Children.ForEach(c => c.Enabled = value);
if (!enabled)
{
Bar.Selected = false;
}
}
}
@@ -113,12 +120,12 @@ namespace Barotrauma
{
get
{
if (ScrollToValue==null) return (BarScroll * (Range.Y - Range.X)) + Range.X;
if (ScrollToValue == null) return (BarScroll * (Range.Y - Range.X)) + Range.X;
return ScrollToValue(this, BarScroll);
}
set
{
if (ValueToScroll==null) BarScroll = (value - Range.X) / (Range.Y - Range.X);
if (ValueToScroll == null) BarScroll = (value - Range.X) / (Range.Y - Range.X);
else BarScroll = ValueToScroll(this, value);
}
}
@@ -189,12 +196,29 @@ namespace Barotrauma
public GUIScrollBar(RectTransform rectT, float barSize = 1, Color? color = null, string style = "", bool? isHorizontal = null) : base(style, rectT)
{
CanBeFocused = true;
this.isHorizontal = isHorizontal ?? (Rect.Width > Rect.Height);
Frame = new GUIFrame(new RectTransform(Vector2.One, rectT));
GUI.Style.Apply(Frame, IsHorizontal ? "GUIFrameHorizontal" : "GUIFrameVertical", this);
this.barSize = barSize;
Bar = new GUIButton(new RectTransform(Vector2.One, rectT, IsHorizontal ? Anchor.CenterLeft : Anchor.TopCenter), color: color);
Bar = new GUIButton(new RectTransform(Vector2.One, rectT, IsHorizontal ? Anchor.CenterLeft : Anchor.TopCenter), color: color, style: null);
switch (style)
{
case "":
HoverCursor = CursorState.Default;
Bar.HoverCursor = CursorState.Default;
break;
case "GUISlider":
HoverCursor = CursorState.Default;
Bar.HoverCursor = CursorState.Hand;
break;
default:
HoverCursor = CursorState.Hand;
Bar.HoverCursor = CursorState.Hand;
break;
}
GUI.Style.Apply(Bar, IsHorizontal ? "GUIButtonHorizontal" : "GUIButtonVertical", this);
Bar.OnPressed = SelectBar;
enabled = true;
@@ -219,12 +243,16 @@ namespace Barotrauma
{
if (!Visible) { return; }
base.Update(deltaTime);
if (!enabled) { return; }
Frame.State = GUI.MouseOn == Frame ? ComponentState.Hover : ComponentState.None;
if (Frame.State == ComponentState.Hover && PlayerInput.PrimaryMouseButtonHeld())
{
Frame.State = ComponentState.Pressed;
}
if (IsBooleanSwitch &&
(!PlayerInput.LeftButtonHeld() || (GUI.MouseOn != this && !IsParentOf(GUI.MouseOn))))
(!PlayerInput.PrimaryMouseButtonHeld() || (GUI.MouseOn != this && !IsParentOf(GUI.MouseOn))))
{
int dir = Math.Sign(barScroll - (minValue + maxValue) / 2.0f);
if (dir == 0) dir = 1;
@@ -235,11 +263,12 @@ namespace Barotrauma
}
}
if (draggingBar == this)
if (DraggingBar == this)
{
GUI.ForceMouseOn(this);
if (dragStartPos == null) { dragStartPos = PlayerInput.MousePosition; }
if (!PlayerInput.LeftButtonHeld())
if (!PlayerInput.PrimaryMouseButtonHeld())
{
if (IsBooleanSwitch && GUI.MouseOn == Bar && Vector2.Distance(dragStartPos.Value, PlayerInput.MousePosition) < 5)
{
@@ -247,7 +276,7 @@ namespace Barotrauma
OnMoved?.Invoke(this, BarScroll);
}
OnReleased?.Invoke(this, BarScroll);
draggingBar = null;
DraggingBar = null;
dragStartPos = null;
}
@@ -259,9 +288,9 @@ namespace Barotrauma
}
else if (GUI.MouseOn == Frame)
{
if (PlayerInput.LeftButtonClicked())
if (PlayerInput.PrimaryMouseButtonClicked())
{
draggingBar?.OnReleased?.Invoke(draggingBar, draggingBar.BarScroll);
DraggingBar?.OnReleased?.Invoke(DraggingBar, DraggingBar.BarScroll);
if (IsBooleanSwitch)
{
MoveButton(new Vector2(
@@ -280,10 +309,10 @@ namespace Barotrauma
private bool SelectBar()
{
if (!enabled || !PlayerInput.LeftButtonDown()) { return false; }
if (!enabled || !PlayerInput.PrimaryMouseButtonDown()) { return false; }
if (barSize >= 1.0f) { return false; }
draggingBar = this;
DraggingBar = this;
return true;
}

View File

@@ -0,0 +1,431 @@
using Barotrauma.Extensions;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Xml.Linq;
namespace Barotrauma
{
public class GUIStyle
{
private Dictionary<string, GUIComponentStyle> componentStyles;
private XElement configElement;
private GraphicsDevice graphicsDevice;
private ScalableFont defaultFont;
public ScalableFont Font { get; private set; }
public ScalableFont GlobalFont { get; private set; }
public ScalableFont UnscaledSmallFont { get; private set; }
public ScalableFont SmallFont { get; private set; }
public ScalableFont LargeFont { get; private set; }
public ScalableFont SubHeadingFont { get; private set; }
public ScalableFont DigitalFont { get; private set; }
public ScalableFont HotkeyFont { get; private set; }
public Dictionary<ScalableFont, bool> ForceFontUpperCase
{
get;
private set;
} = new Dictionary<ScalableFont, bool>();
public readonly Sprite[] CursorSprite = new Sprite[7];
public UISprite UIGlow { get; private set; }
public UISprite UIGlowCircular { get; private set; }
public SpriteSheet FocusIndicator { get; private set; }
/// <summary>
/// General green color used for elements whose colors are set from code
/// </summary>
public Color Green { get; private set; } = Color.LightGreen;
/// <summary>
/// General red color used for elements whose colors are set from code
/// </summary>
public Color Orange { get; private set; } = Color.Orange;
/// <summary>
/// General red color used for elements whose colors are set from code
/// </summary>
public Color Red { get; private set; } = Color.Red;
/// <summary>
/// General blue color used for elements whose colors are set from code
/// </summary>
public Color Blue { get; private set; } = Color.Blue;
public Color ColorInventoryEmpty { get; private set; } = Color.Red;
public Color ColorInventoryHalf { get; private set; } = Color.Orange;
public Color ColorInventoryFull { get; private set; } = Color.LightGreen;
public Color ColorInventoryBackground { get; private set; } = Color.Gray;
public Color TextColor { get; private set; } = Color.White * 0.8f;
public Color TextColorBright { get; private set; } = Color.White * 0.9f;
public Color TextColorDark { get; private set; } = Color.Black * 0.9f;
public Color TextColorDim { get; private set; } = Color.White * 0.6f;
// Inventory
public Color EquipmentSlotIconColor { get; private set; } = new Color(99, 70, 64);
// Health HUD
public Color BuffColorLow { get; private set; } = Color.LightGreen;
public Color BuffColorMedium { get; private set; } = Color.Green;
public Color BuffColorHigh { get; private set; } = Color.DarkGreen;
public Color DebuffColorLow { get; private set; } = Color.DarkSalmon;
public Color DebuffColorMedium { get; private set; } = Color.Red;
public Color DebuffColorHigh { get; private set; } = Color.DarkRed;
public Color HealthBarColorLow { get; private set; } = Color.Red;
public Color HealthBarColorMedium { get; private set; } = Color.Orange;
public Color HealthBarColorHigh { get; private set; } = new Color(78, 114, 88);
public Color EquipmentIndicatorNotEquipped { get; private set; } = Color.Gray;
public Color EquipmentIndicatorEquipped { get; private set; } = new Color(105, 202, 125);
public Color EquipmentIndicatorRunningOut { get; private set; } = new Color(202, 105, 105);
public static Point ItemFrameMargin => new Point(50, 56).Multiply(GUI.SlicedSpriteScale);
public static Point ItemFrameOffset => new Point(0, 3).Multiply(GUI.SlicedSpriteScale);
public GUIStyle(XElement element, GraphicsDevice graphicsDevice)
{
this.graphicsDevice = graphicsDevice;
componentStyles = new Dictionary<string, GUIComponentStyle>();
configElement = element;
foreach (XElement subElement in configElement.Elements())
{
var name = subElement.Name.ToString().ToLowerInvariant();
switch (name)
{
case "cursor":
if (subElement.HasElements)
{
foreach (var children in subElement.Descendants())
{
var index = children.GetAttributeInt("state", (int)CursorState.Default);
CursorSprite[index] = new Sprite(children);
}
}
else
{
CursorSprite[(int)CursorState.Default] = new Sprite(subElement);
}
break;
case "green":
Green = subElement.GetAttributeColor("color", Green);
break;
case "orange":
Orange = subElement.GetAttributeColor("color", Orange);
break;
case "red":
Red = subElement.GetAttributeColor("color", Red);
break;
case "blue":
Blue = subElement.GetAttributeColor("color", Blue);
break;
case "colorinventoryempty":
ColorInventoryEmpty = subElement.GetAttributeColor("color", ColorInventoryEmpty);
break;
case "colorinventoryhalf":
ColorInventoryHalf = subElement.GetAttributeColor("color", ColorInventoryHalf);
break;
case "colorinventoryfull":
ColorInventoryFull = subElement.GetAttributeColor("color", ColorInventoryFull);
break;
case "colorinventorybackground":
ColorInventoryBackground = subElement.GetAttributeColor("color", ColorInventoryBackground);
break;
case "textcolordark":
TextColorDark = subElement.GetAttributeColor("color", TextColorDark);
break;
case "textcolorbright":
TextColorBright = subElement.GetAttributeColor("color", TextColorBright);
break;
case "textcolordim":
TextColorDim = subElement.GetAttributeColor("color", TextColorDim);
break;
case "textcolornormal":
case "textcolor":
TextColor = subElement.GetAttributeColor("color", TextColor);
break;
case "equipmentsloticoncolor":
EquipmentSlotIconColor = subElement.GetAttributeColor("color", EquipmentSlotIconColor);
break;
case "buffcolorlow":
BuffColorLow = subElement.GetAttributeColor("color", BuffColorLow);
break;
case "buffcolormedium":
BuffColorMedium = subElement.GetAttributeColor("color", BuffColorMedium);
break;
case "buffcolorhigh":
BuffColorHigh = subElement.GetAttributeColor("color", BuffColorHigh);
break;
case "debuffcolorlow":
DebuffColorLow = subElement.GetAttributeColor("color", DebuffColorLow);
break;
case "debuffcolormedium":
DebuffColorMedium = subElement.GetAttributeColor("color", DebuffColorMedium);
break;
case "debuffcolorhigh":
DebuffColorHigh = subElement.GetAttributeColor("color", DebuffColorHigh);
break;
case "healthbarcolorlow":
HealthBarColorLow = subElement.GetAttributeColor("color", HealthBarColorLow);
break;
case "healthbarcolormedium":
HealthBarColorMedium = subElement.GetAttributeColor("color", HealthBarColorMedium);
break;
case "healthbarcolorhigh":
HealthBarColorHigh = subElement.GetAttributeColor("color", HealthBarColorHigh);
break;
case "equipmentindicatornotequipped":
EquipmentIndicatorNotEquipped = subElement.GetAttributeColor("color", EquipmentIndicatorNotEquipped);
break;
case "equipmentindicatorequipped":
EquipmentIndicatorEquipped = subElement.GetAttributeColor("color", EquipmentIndicatorEquipped);
break;
case "equipmentindicatorrunningout":
EquipmentIndicatorRunningOut = subElement.GetAttributeColor("color", EquipmentIndicatorRunningOut);
break;
case "uiglow":
UIGlow = new UISprite(subElement);
break;
case "uiglowcircular":
UIGlowCircular = new UISprite(subElement);
break;
case "focusindicator":
FocusIndicator = new SpriteSheet(subElement);
break;
case "font":
Font = LoadFont(subElement, graphicsDevice);
ForceFontUpperCase[Font] = subElement.GetAttributeBool("forceuppercase", false);
break;
case "globalfont":
GlobalFont = LoadFont(subElement, graphicsDevice);
ForceFontUpperCase[GlobalFont] = subElement.GetAttributeBool("forceuppercase", false);
break;
case "unscaledsmallfont":
UnscaledSmallFont = LoadFont(subElement, graphicsDevice);
ForceFontUpperCase[UnscaledSmallFont] = subElement.GetAttributeBool("forceuppercase", false);
break;
case "smallfont":
SmallFont = LoadFont(subElement, graphicsDevice);
ForceFontUpperCase[SmallFont] = subElement.GetAttributeBool("forceuppercase", false);
break;
case "largefont":
LargeFont = LoadFont(subElement, graphicsDevice);
ForceFontUpperCase[LargeFont] = subElement.GetAttributeBool("forceuppercase", false);
break;
case "digitalfont":
DigitalFont = LoadFont(subElement, graphicsDevice);
ForceFontUpperCase[DigitalFont] = subElement.GetAttributeBool("forceuppercase", false);
break;
case "hotkeyfont":
HotkeyFont = LoadFont(subElement, graphicsDevice);
ForceFontUpperCase[HotkeyFont] = subElement.GetAttributeBool("forceuppercase", false);
break;
case "objectivetitle":
case "subheading":
SubHeadingFont = LoadFont(subElement, graphicsDevice);
ForceFontUpperCase[SubHeadingFont] = subElement.GetAttributeBool("forceuppercase", false);
break;
default:
GUIComponentStyle componentStyle = new GUIComponentStyle(subElement, this);
componentStyles.Add(subElement.Name.ToString().ToLowerInvariant(), componentStyle);
break;
}
}
if (GlobalFont == null)
{
GlobalFont = Font;
DebugConsole.NewMessage("Global font not defined in the current UI style file. The global font is used to render western symbols when using Chinese/Japanese/Korean localization. Using default font instead...", Color.Orange);
}
GameMain.Instance.OnResolutionChanged += () => { RescaleElements(); };
}
/// <summary>
/// Returns the default font of the currently selected language
/// </summary>
public ScalableFont LoadCurrentDefaultFont()
{
defaultFont?.Dispose();
defaultFont = null;
foreach (XElement subElement in configElement.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "font":
defaultFont = LoadFont(subElement, graphicsDevice);
break;
}
}
return defaultFont;
}
private void RescaleElements()
{
if (configElement == null) { return; }
if (configElement.Elements() == null) { return; }
foreach (XElement subElement in configElement.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "font":
if (Font == null) { continue; }
Font.Size = GetFontSize(subElement);
break;
case "smallfont":
if (SmallFont == null) { continue; }
SmallFont.Size = GetFontSize(subElement);
break;
case "largefont":
if (LargeFont == null) { continue; }
LargeFont.Size = GetFontSize(subElement);
break;
case "hotkeyfont":
if (HotkeyFont == null) { continue; }
HotkeyFont.Size = GetFontSize(subElement);
break;
case "objectivetitle":
case "subheading":
if (SubHeadingFont == null) { continue; }
SubHeadingFont.Size = GetFontSize(subElement);
break;
}
}
foreach (var componentStyle in componentStyles.Values)
{
componentStyle.GetSize(componentStyle.Element);
foreach (var childStyle in componentStyle.ChildStyles.Values)
{
childStyle.GetSize(childStyle.Element);
}
}
}
private ScalableFont LoadFont(XElement element, GraphicsDevice graphicsDevice)
{
string file = GetFontFilePath(element);
uint size = GetFontSize(element);
bool dynamicLoading = GetFontDynamicLoading(element);
bool isCJK = GetIsCJK(element);
return new ScalableFont(file, size, graphicsDevice, dynamicLoading, isCJK);
}
private uint GetFontSize(XElement element, uint defaultSize = 14)
{
//check if any of the language override fonts want to override the font size as well
foreach (XElement subElement in element.Elements())
{
if (!subElement.Name.ToString().Equals("override", StringComparison.OrdinalIgnoreCase)) { continue; }
if (GameMain.Config.Language.Equals(subElement.GetAttributeString("language", ""), StringComparison.OrdinalIgnoreCase))
{
uint overrideFontSize = GetFontSize(subElement, 0);
if (overrideFontSize > 0) { return overrideFontSize; }
}
}
foreach (XElement subElement in element.Elements())
{
if (!subElement.Name.ToString().Equals("size", StringComparison.OrdinalIgnoreCase)) { continue; }
Point maxResolution = subElement.GetAttributePoint("maxresolution", new Point(int.MaxValue, int.MaxValue));
if (GameMain.GraphicsWidth <= maxResolution.X && GameMain.GraphicsHeight <= maxResolution.Y)
{
return (uint)subElement.GetAttributeInt("size", 14);
}
}
return defaultSize;
}
private string GetFontFilePath(XElement element)
{
foreach (XElement subElement in element.Elements())
{
if (!subElement.Name.ToString().Equals("override", StringComparison.OrdinalIgnoreCase)) { continue; }
if (GameMain.Config.Language.Equals(subElement.GetAttributeString("language", ""), StringComparison.OrdinalIgnoreCase))
{
return subElement.GetAttributeString("file", "");
}
}
return element.GetAttributeString("file", "");
}
private bool GetFontDynamicLoading(XElement element)
{
foreach (XElement subElement in element.Elements())
{
if (!subElement.Name.ToString().Equals("override", StringComparison.OrdinalIgnoreCase)) { continue; }
if (GameMain.Config.Language.Equals(subElement.GetAttributeString("language", ""), StringComparison.OrdinalIgnoreCase))
{
return subElement.GetAttributeBool("dynamicloading", false);
}
}
return element.GetAttributeBool("dynamicloading", false);
}
private bool GetIsCJK(XElement element)
{
foreach (XElement subElement in element.Elements())
{
if (!subElement.Name.ToString().Equals("override", StringComparison.OrdinalIgnoreCase)) { continue; }
if (GameMain.Config.Language.Equals(subElement.GetAttributeString("language", ""), StringComparison.OrdinalIgnoreCase))
{
return subElement.GetAttributeBool("iscjk", false);
}
}
return element.GetAttributeBool("iscjk", false);
}
public GUIComponentStyle GetComponentStyle(string name)
{
componentStyles.TryGetValue(name.ToLowerInvariant(), out GUIComponentStyle style);
return style;
}
public void Apply(GUIComponent targetComponent, string styleName = "", GUIComponent parent = null)
{
GUIComponentStyle componentStyle = null;
if (parent != null)
{
GUIComponentStyle parentStyle = parent.Style;
if (parent.Style == null)
{
string parentStyleName = parent.GetType().Name.ToLowerInvariant();
if (!componentStyles.TryGetValue(parentStyleName, out parentStyle))
{
DebugConsole.ThrowError("Couldn't find a GUI style \""+ parentStyleName + "\"");
return;
}
}
string childStyleName = string.IsNullOrEmpty(styleName) ? targetComponent.GetType().Name : styleName;
parentStyle.ChildStyles.TryGetValue(childStyleName.ToLowerInvariant(), out componentStyle);
}
else
{
if (string.IsNullOrEmpty(styleName))
{
styleName = targetComponent.GetType().Name;
}
if (!componentStyles.TryGetValue(styleName.ToLowerInvariant(), out componentStyle))
{
DebugConsole.ThrowError("Couldn't find a GUI style \""+ styleName+"\"");
return;
}
}
targetComponent.ApplyStyle(componentStyle);
}
}
}

View File

@@ -0,0 +1,535 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
public class GUITextBlock : GUIComponent
{
protected string text;
protected Alignment textAlignment;
private float textScale = 1;
protected Vector2 textPos;
protected Vector2 origin;
protected Color textColor, disabledTextColor, selectedTextColor;
private string wrappedText;
private string censoredText;
public delegate string TextGetterHandler();
public TextGetterHandler TextGetter;
public bool Wrap;
private bool playerInput;
public bool RoundToNearestPixel = true;
private bool overflowClipActive;
public bool OverflowClip;
public bool OverflowClipActive
{
get { return overflowClipActive; }
}
private float textDepth;
private ScalableFont originalFont;
public Vector2 TextOffset { get; set; }
private Vector4 padding;
public Vector4 Padding
{
get { return padding; }
set
{
padding = value;
SetTextPos();
}
}
public override ScalableFont Font
{
get
{
return base.Font;
}
set
{
if (base.Font == value) { return; }
base.Font = originalFont = value;
if (text != null && GUI.Style.ForceFontUpperCase.ContainsKey(Font) && GUI.Style.ForceFontUpperCase[Font])
{
Text = text.ToUpper();
}
SetTextPos();
}
}
public string Text
{
get { return text; }
set
{
string newText = forceUpperCase || (GUI.Style.ForceFontUpperCase.ContainsKey(Font) && GUI.Style.ForceFontUpperCase[Font]) || (style != null && style.ForceUpperCase) ?
value?.ToUpper() :
value;
if (Text == newText) { return; }
//reset scale, it gets recalculated in SetTextPos
if (autoScaleHorizontal || autoScaleVertical) { textScale = 1.0f; }
text = newText;
wrappedText = newText;
if (TextManager.IsCJK(text))
{
//switch to fallback CJK font
if (!Font.IsCJK) { base.Font = GUI.CJKFont; }
}
else
{
if (Font == GUI.CJKFont) { base.Font = originalFont; }
}
SetTextPos();
}
}
public string WrappedText
{
get { return wrappedText; }
}
public float TextDepth
{
get { return textDepth; }
set { textDepth = MathHelper.Clamp(value, 0.0f, 1.0f); }
}
public Vector2 TextPos
{
get { return textPos; }
set { textPos = value; }
}
public float TextScale
{
get { return textScale; }
set
{
if (value != textScale)
{
textScale = value;
SetTextPos();
}
}
}
private bool autoScaleHorizontal, autoScaleVertical;
/// <summary>
/// When enabled, the text is automatically scaled down to fit the textblock horizontally.
/// </summary>
public bool AutoScaleHorizontal
{
get { return autoScaleHorizontal; }
set
{
if (autoScaleHorizontal == value) { return; }
autoScaleHorizontal = value;
if (autoScaleHorizontal)
{
SetTextPos();
}
}
}
/// <summary>
/// When enabled, the text is automatically scaled down to fit the textblock vertically.
/// </summary>
public bool AutoScaleVertical
{
get { return autoScaleVertical; }
set
{
if (autoScaleVertical == value) { return; }
autoScaleVertical = value;
if (autoScaleVertical)
{
SetTextPos();
}
}
}
private bool forceUpperCase;
public bool ForceUpperCase
{
get { return forceUpperCase; }
set
{
if (forceUpperCase == value) { return; }
forceUpperCase = value;
if (forceUpperCase ||
(style != null && style.ForceUpperCase) ||
(GUI.Style.ForceFontUpperCase.ContainsKey(Font) && GUI.Style.ForceFontUpperCase[Font]))
{
Text = text?.ToUpper();
}
}
}
public Vector2 Origin
{
get { return origin; }
}
public Vector2 TextSize
{
get;
private set;
}
public Color TextColor
{
get { return textColor; }
set { textColor = value; }
}
private Color? hoverTextColor;
public Color HoverTextColor
{
get { return hoverTextColor ?? textColor; }
set { hoverTextColor = value; }
}
public Color SelectedTextColor
{
get { return selectedTextColor; }
set { selectedTextColor = value; }
}
public Alignment TextAlignment
{
get { return textAlignment; }
set
{
if (textAlignment == value) return;
textAlignment = value;
SetTextPos();
}
}
public bool Censor
{
get;
set;
}
public string CensoredText
{
get { return censoredText; }
}
private List<ColorData> colorData = null;
private bool hasColorHighlight = false;
/// <summary>
/// This is the new constructor.
/// If the rectT height is set 0, the height is calculated from the text.
/// </summary>
public GUITextBlock(RectTransform rectT, string text, Color? textColor = null, ScalableFont font = null,
Alignment textAlignment = Alignment.Left, bool wrap = false, string style = "", Color? color = null, bool playerInput = false)
: base(style, rectT)
{
if (color.HasValue)
{
this.color = color.Value;
}
if (textColor.HasValue)
{
OverrideTextColor(textColor.Value);
}
//if the text is in chinese/korean/japanese and we're not using a CJK-compatible font,
//use the default CJK font as a fallback
var selectedFont = originalFont = font ?? GUI.Font;
if (TextManager.IsCJK(text) && !selectedFont.IsCJK)
{
selectedFont = GUI.CJKFont;
}
this.Font = selectedFont;
this.textAlignment = textAlignment;
this.Wrap = wrap;
this.Text = text ?? "";
this.playerInput = playerInput;
if (rectT.Rect.Height == 0 && !string.IsNullOrEmpty(text))
{
CalculateHeightFromText();
}
SetTextPos();
RectTransform.ScaleChanged += SetTextPos;
RectTransform.SizeChanged += SetTextPos;
Enabled = true;
Censor = false;
}
public GUITextBlock(RectTransform rectT, List<ColorData> colorData, string text, Color? textColor = null, ScalableFont font = null, Alignment textAlignment = Alignment.Left, bool wrap = false, string style = "", Color? color = null, bool playerInput = false)
: this(rectT, text, textColor, font, textAlignment, wrap, style, color, playerInput)
{
this.colorData = colorData;
hasColorHighlight = colorData != null;
}
public void CalculateHeightFromText(int padding = 0)
{
if (wrappedText == null) { return; }
RectTransform.Resize(new Point(RectTransform.Rect.Width, (int)Font.MeasureString(wrappedText).Y + padding));
}
public override void ApplyStyle(GUIComponentStyle componentStyle)
{
if (componentStyle == null) { return; }
base.ApplyStyle(componentStyle);
padding = componentStyle.Padding;
textColor = componentStyle.TextColor;
hoverTextColor = componentStyle.HoverTextColor;
disabledTextColor = componentStyle.DisabledTextColor;
selectedTextColor = componentStyle.SelectedTextColor;
switch (componentStyle.Font)
{
case "font":
Font = componentStyle.Style.Font;
break;
case "smallfont":
Font = componentStyle.Style.SmallFont;
break;
case "largefont":
Font = componentStyle.Style.LargeFont;
break;
case "objectivetitle":
case "subheading":
Font = componentStyle.Style.SubHeadingFont;
break;
}
}
public void SetTextPos()
{
if (text == null) { return; }
censoredText = string.IsNullOrEmpty(text) ? "" : new string('\u2022', text.Length);
var rect = Rect;
overflowClipActive = false;
wrappedText = text;
TextSize = MeasureText(text);
if (Wrap && rect.Width > 0)
{
wrappedText = ToolBox.WrapText(text, rect.Width - padding.X - padding.Z, Font, textScale, playerInput);
TextSize = MeasureText(wrappedText);
}
else if (OverflowClip)
{
overflowClipActive = TextSize.X > rect.Width - padding.X - padding.Z;
}
Vector2 minSize = new Vector2(
Math.Max(rect.Width - padding.X - padding.Z, 5.0f),
Math.Max(rect.Height - padding.Y - padding.W, 5.0f));
if (!autoScaleHorizontal) { minSize.X = float.MaxValue; }
if (!Wrap && !autoScaleVertical) { minSize.Y = float.MaxValue; }
if ((autoScaleHorizontal || autoScaleVertical) && textScale > 0.1f &&
(TextSize.X * textScale > minSize.X || TextSize.Y * textScale > minSize.Y))
{
TextScale = Math.Max(0.1f, Math.Min(minSize.X / TextSize.X, minSize.Y / TextSize.Y)) - 0.01f;
return;
}
textPos = new Vector2(padding.X + (rect.Width - padding.Z - padding.X) / 2.0f, padding.Y + (rect.Height - padding.Y - padding.W) / 2.0f);
origin = TextSize * 0.5f;
if (textAlignment.HasFlag(Alignment.Left) && !overflowClipActive)
{
textPos.X = padding.X;
origin.X = 0;
}
if (textAlignment.HasFlag(Alignment.Right) || overflowClipActive)
{
textPos.X = rect.Width - padding.Z;
origin.X = TextSize.X;
}
if (textAlignment.HasFlag(Alignment.Top))
{
textPos.Y = padding.Y;
origin.Y = 0;
}
if (textAlignment.HasFlag(Alignment.Bottom))
{
textPos.Y = rect.Height - padding.W;
origin.Y = TextSize.Y;
}
origin.X = (int)(origin.X);
origin.Y = (int)(origin.Y);
textPos.X = (int)textPos.X;
textPos.Y = (int)textPos.Y;
}
private Vector2 MeasureText(string text)
{
if (Font == null) return Vector2.Zero;
if (string.IsNullOrEmpty(text))
{
return Font.MeasureString(" ");
}
Vector2 size = Vector2.Zero;
while (size == Vector2.Zero)
{
try { size = Font.MeasureString(string.IsNullOrEmpty(text) ? " " : text); }
catch { text = text.Substring(0, text.Length - 1); }
}
return size;
}
protected override void SetAlpha(float a)
{
base.SetAlpha(a);
textColor = new Color(textColor.R, textColor.G, textColor.B, a);
}
/// <summary>
/// Overrides the color for all the states.
/// </summary>
public void OverrideTextColor(Color color)
{
textColor = color;
hoverTextColor = color;
selectedTextColor = color;
disabledTextColor = color;
}
protected override void Draw(SpriteBatch spriteBatch)
{
if (!Visible) { return; }
Color currColor = GetColor(State);
var rect = Rect;
base.Draw(spriteBatch);
if (TextGetter != null) { Text = TextGetter(); }
Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle;
if (overflowClipActive)
{
spriteBatch.End();
Rectangle scissorRect = new Rectangle(rect.X + (int)padding.X, rect.Y, rect.Width - (int)padding.X - (int)padding.Z, rect.Height);
spriteBatch.GraphicsDevice.ScissorRectangle = scissorRect;
spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable);
}
if (!string.IsNullOrEmpty(text))
{
Vector2 pos = rect.Location.ToVector2() + textPos + TextOffset;
if (RoundToNearestPixel)
{
pos.X = (int)pos.X;
pos.Y = (int)pos.Y;
}
Color currentTextColor = State == ComponentState.Hover || State == ComponentState.HoverSelected ? HoverTextColor : TextColor;
if (!enabled)
{
currentTextColor = disabledTextColor;
}
else if (State == ComponentState.Selected)
{
currentTextColor = selectedTextColor;
}
if (!hasColorHighlight)
{
Font.DrawString(spriteBatch,
Censor ? censoredText : (Wrap ? wrappedText : text),
pos,
currentTextColor * (currentTextColor.A / 255.0f),
0.0f, origin, TextScale,
SpriteEffects.None, textDepth);
}
else
{
Font.DrawStringWithColors(spriteBatch, Censor ? censoredText : (Wrap ? wrappedText : text), pos,
currentTextColor * (currentTextColor.A / 255.0f), 0.0f, origin, TextScale, SpriteEffects.None, textDepth, colorData);
}
}
if (overflowClipActive)
{
spriteBatch.End();
spriteBatch.GraphicsDevice.ScissorRectangle = prevScissorRect;
spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable);
}
if (OutlineColor.A * currColor.A > 0.0f) GUI.DrawRectangle(spriteBatch, rect, OutlineColor * (currColor.A / 255.0f), false);
}
/// <summary>
/// Set the text scale of the GUITextBlocks so that they all use the same scale and can fit the text within the block.
/// </summary>
public static void AutoScaleAndNormalize(params GUITextBlock[] textBlocks)
{
AutoScaleAndNormalize(textBlocks.AsEnumerable<GUITextBlock>());
}
/// <summary>
/// Set the text scale of the GUITextBlocks so that they all use the same scale and can fit the text within the block.
/// </summary>
public static void AutoScaleAndNormalize(bool scaleHorizontal = true, bool scaleVertical = false, params GUITextBlock[] textBlocks)
{
AutoScaleAndNormalize(textBlocks.AsEnumerable<GUITextBlock>(), scaleHorizontal, scaleVertical);
}
/// <summary>
/// Set the text scale of the GUITextBlocks so that they all use the same scale and can fit the text within the block.
/// </summary>
public static void AutoScaleAndNormalize(IEnumerable<GUITextBlock> textBlocks, bool scaleHorizontal = true, bool scaleVertical = false, float? defaultScale = null)
{
if (!textBlocks.Any()) { return; }
float minScale = Math.Max(textBlocks.First().TextScale, 1.0f);
foreach (GUITextBlock textBlock in textBlocks)
{
if (defaultScale.HasValue) { textBlock.TextScale = defaultScale.Value; }
textBlock.AutoScaleHorizontal = scaleHorizontal;
textBlock.AutoScaleVertical = scaleVertical;
minScale = Math.Min(textBlock.TextScale, minScale);
}
foreach (GUITextBlock textBlock in textBlocks)
{
textBlock.AutoScaleHorizontal = false;
textBlock.AutoScaleVertical = false;
textBlock.TextScale = minScale;
}
}
}
}

View File

@@ -19,8 +19,9 @@ namespace Barotrauma
bool caretVisible;
float caretTimer;
private GUIFrame frame;
private GUITextBlock textBlock;
private readonly GUIFrame frame;
private readonly GUITextBlock textBlock;
private readonly GUIImage icon;
public Func<string, string> textFilterFunction;
@@ -48,7 +49,6 @@ namespace Barotrauma
get { return _caretIndex; }
set
{
previousCaretIndex = _caretIndex;
_caretIndex = value;
caretPosDirty = true;
}
@@ -63,7 +63,6 @@ namespace Barotrauma
private int selectionStartIndex;
private int selectionEndIndex;
private bool IsLeftToRight => selectionStartIndex <= selectionEndIndex;
private int previousCaretIndex;
private Vector2 selectionStartPos;
private Vector2 selectionEndPos;
private Vector2 selectionRectSize;
@@ -76,8 +75,8 @@ namespace Barotrauma
set { textBlock.TextGetter = value; }
}
private bool selected;
public bool Selected
private new bool selected;
public new bool Selected
{
get
{
@@ -105,6 +104,11 @@ namespace Barotrauma
}
}
public GUITextBlock TextBlock
{
get { return textBlock; }
}
//should the text be limited to the size of the box
//ignored when MaxTextLength is set or text wrapping is enabled
public bool ClampText
@@ -134,7 +138,8 @@ namespace Barotrauma
get { return enabled; }
set
{
enabled = value;
enabled = frame.Enabled = textBlock.Enabled = value;
if (icon != null) { icon.Enabled = value; }
if (!enabled && Selected)
{
Deselect();
@@ -229,25 +234,61 @@ namespace Barotrauma
get { return textBlock.WrappedText; }
}
public bool Readonly { get; set; }
public GUITextBox(RectTransform rectT, string text = "", Color? textColor = null, ScalableFont font = null,
Alignment textAlignment = Alignment.Left, bool wrap = false, string style = "", Color? color = null)
Alignment textAlignment = Alignment.Left, bool wrap = false, string style = "", Color? color = null, bool createClearButton = false)
: base(style, rectT)
{
HoverCursor = CursorState.IBeam;
CanBeFocused = true;
Enabled = true;
this.color = color ?? Color.White;
frame = new GUIFrame(new RectTransform(Vector2.One, rectT, Anchor.Center), style, color);
GUI.Style.Apply(frame, style == "" ? "GUITextBox" : style);
textBlock = new GUITextBlock(new RectTransform(Vector2.One, frame.RectTransform, Anchor.Center), text, textColor, font, textAlignment, wrap, playerInput: true);
textBlock = new GUITextBlock(new RectTransform(Vector2.One, frame.RectTransform, Anchor.CenterLeft), text, textColor, font, textAlignment, wrap, playerInput: true);
GUI.Style.Apply(textBlock, "", this);
CaretEnabled = true;
caretPosDirty = true;
new GUICustomComponent(new RectTransform(Vector2.One, frame.RectTransform), onDraw: DrawCaretAndSelection);
int clearButtonWidth = 0;
if (createClearButton)
{
var clearButton = new GUIButton(new RectTransform(new Vector2(0.6f, 0.6f), frame.RectTransform, Anchor.CenterRight, scaleBasis: ScaleBasis.BothHeight) { AbsoluteOffset = new Point(5, 0) }, style: "GUICancelButton")
{
OnClicked = (bt, userdata) =>
{
Text = "";
frame.Flash(Color.White);
return true;
}
};
textBlock.RectTransform.MaxSize = new Point(frame.Rect.Width - clearButton.Rect.Height - clearButton.RectTransform.AbsoluteOffset.X * 2, int.MaxValue);
clearButtonWidth = (int)(clearButton.Rect.Width * 1.2f);
}
if (this.style != null && this.style.ChildStyles.ContainsKey("textboxicon"))
{
icon = new GUIImage(new RectTransform(new Vector2(0.6f, 0.6f), frame.RectTransform, Anchor.CenterRight, scaleBasis: ScaleBasis.BothHeight) { AbsoluteOffset = new Point(5 + clearButtonWidth, 0) }, null, scaleToFit: true);
icon.ApplyStyle(this.style.ChildStyles["textboxicon"]);
textBlock.RectTransform.MaxSize = new Point(frame.Rect.Width - icon.Rect.Height - clearButtonWidth - icon.RectTransform.AbsoluteOffset.X * 2, int.MaxValue);
}
Font = textBlock.Font;
rectT.SizeChanged += () => { caretPosDirty = true; };
rectT.ScaleChanged += () => { caretPosDirty = true; };
Enabled = true;
rectT.SizeChanged += () =>
{
if (icon != null) { textBlock.RectTransform.MaxSize = new Point(frame.Rect.Width - icon.Rect.Height - icon.RectTransform.AbsoluteOffset.X * 2, int.MaxValue); }
caretPosDirty = true;
};
rectT.ScaleChanged += () =>
{
if (icon != null) { textBlock.RectTransform.MaxSize = new Point(frame.Rect.Width - icon.Rect.Height - icon.RectTransform.AbsoluteOffset.X * 2, int.MaxValue); }
caretPosDirty = true;
};
}
private bool SetText(string text, bool store = true)
@@ -417,13 +458,13 @@ namespace Barotrauma
return currPosition != null ? currPosition.Item2 : textBlock.Text.Length;
}
public void Select()
public void Select(int forcedCaretIndex = -1)
{
if (memento.Current == null)
{
memento.Store(Text);
}
CaretIndex = GetCaretIndexFromScreenPos(PlayerInput.MousePosition);
CaretIndex = forcedCaretIndex == - 1 ? GetCaretIndexFromScreenPos(PlayerInput.MousePosition) : forcedCaretIndex;
ClearSelection();
selected = true;
GUI.KeyboardDispatcher.Subscriber = this;
@@ -442,9 +483,9 @@ namespace Barotrauma
OnDeselected?.Invoke(this, Keys.None);
}
public override void Flash(Color? color = null, float flashDuration = 1.5f, bool useRectangleFlash = false, Vector2? flashRectOffset = null)
public override void Flash(Color? color = null, float flashDuration = 1.5f, bool useRectangleFlash = false, bool useCircularFlash = false, Vector2? flashRectOffset = null)
{
textBlock.Flash(color, flashDuration, useRectangleFlash, flashRectOffset);
frame.Flash(color, flashDuration, useRectangleFlash, useCircularFlash, flashRectOffset);
}
protected override void Update(float deltaTime)
@@ -452,17 +493,17 @@ namespace Barotrauma
if (!Visible) return;
if (flashTimer > 0.0f) flashTimer -= deltaTime;
if (!Enabled) return;
if (MouseRect.Contains(PlayerInput.MousePosition) && (GUI.MouseOn == null || GUI.IsMouseOn(this)))
if (!Enabled) { return; }
if (MouseRect.Contains(PlayerInput.MousePosition) && (GUI.MouseOn == null || (!(GUI.MouseOn is GUIButton) && GUI.IsMouseOn(this))))
{
state = ComponentState.Hover;
if (PlayerInput.LeftButtonDown())
State = ComponentState.Hover;
if (PlayerInput.PrimaryMouseButtonDown())
{
Select();
}
else
{
isSelecting = PlayerInput.LeftButtonHeld();
isSelecting = PlayerInput.PrimaryMouseButtonHeld();
}
if (PlayerInput.DoubleClicked())
{
@@ -482,7 +523,7 @@ namespace Barotrauma
{
if ((PlayerInput.LeftButtonClicked() || PlayerInput.RightButtonClicked()) && selected) Deselect();
isSelecting = false;
state = ComponentState.None;
State = ComponentState.None;
}
if (!isSelecting)
{
@@ -493,14 +534,14 @@ namespace Barotrauma
{
if (textBlock.OverflowClipActive)
{
if (CaretScreenPos.X < Rect.X + textBlock.Padding.X)
if (CaretScreenPos.X < textBlock.Rect.X + textBlock.Padding.X)
{
textBlock.TextPos = new Vector2(textBlock.TextPos.X + ((Rect.X + textBlock.Padding.X) - CaretScreenPos.X), textBlock.TextPos.Y);
textBlock.TextPos = new Vector2(textBlock.TextPos.X + ((textBlock.Rect.X + textBlock.Padding.X) - CaretScreenPos.X), textBlock.TextPos.Y);
CalculateCaretPos();
}
else if (CaretScreenPos.X > Rect.Right - textBlock.Padding.Z)
else if (CaretScreenPos.X > textBlock.Rect.Right - textBlock.Padding.Z)
{
textBlock.TextPos = new Vector2(textBlock.TextPos.X - (CaretScreenPos.X - (Rect.Right - textBlock.Padding.Z)), textBlock.TextPos.Y);
textBlock.TextPos = new Vector2(textBlock.TextPos.X - (CaretScreenPos.X - (textBlock.Rect.Right - textBlock.Padding.Z)), textBlock.TextPos.Y);
CalculateCaretPos();
}
}
@@ -514,7 +555,7 @@ namespace Barotrauma
if (GUI.KeyboardDispatcher.Subscriber == this)
{
state = ComponentState.Selected;
State = ComponentState.Selected;
Character.DisableControls = true;
if (OnEnterPressed != null && PlayerInput.KeyHit(Keys.Enter))
{
@@ -526,16 +567,12 @@ namespace Barotrauma
Deselect();
}
textBlock.State = state;
textBlock.State = State;
}
protected override void Draw(SpriteBatch spriteBatch)
private void DrawCaretAndSelection(SpriteBatch spriteBatch, GUICustomComponent customComponent)
{
if (!Visible) return;
base.Draw(spriteBatch);
// Frame is not used in the old system.
frame?.DrawManually(spriteBatch);
textBlock.DrawManually(spriteBatch);
if (!Visible) { return; }
if (Selected)
{
if (caretVisible )
@@ -553,9 +590,9 @@ namespace Barotrauma
//GUI.DrawString(spriteBatch, new Vector2(100, 20), selectionStartIndex.ToString(), Color.White, Color.Black);
//GUI.DrawString(spriteBatch, new Vector2(140, 20), selectionEndIndex.ToString(), Color.White, Color.Black);
//GUI.DrawString(spriteBatch, new Vector2(100, 40), selectedText.ToString(), Color.Yellow, Color.Black);
//GUI.DrawString(spriteBatch, new Vector2(100, 60), $"caret index: {CaretIndex.ToString()}", Color.Red, Color.Black);
//GUI.DrawString(spriteBatch, new Vector2(100, 80), $"caret pos: {caretPos.ToString()}", Color.Red, Color.Black);
//GUI.DrawString(spriteBatch, new Vector2(100, 100), $"caret screen pos: {CaretScreenPos.ToString()}", Color.Red, Color.Black);
//GUI.DrawString(spriteBatch, new Vector2(100, 60), $"caret index: {CaretIndex.ToString()}", GUI.Style.Red, Color.Black);
//GUI.DrawString(spriteBatch, new Vector2(100, 80), $"caret pos: {caretPos.ToString()}", GUI.Style.Red, Color.Black);
//GUI.DrawString(spriteBatch, new Vector2(100, 100), $"caret screen pos: {CaretScreenPos.ToString()}", GUI.Style.Red, Color.Black);
//GUI.DrawString(spriteBatch, new Vector2(100, 120), $"text start pos: {(textBlock.TextPos - textBlock.Origin).ToString()}", Color.White, Color.Black);
//GUI.DrawString(spriteBatch, new Vector2(100, 140), $"cursor pos: {PlayerInput.MousePosition.ToString()}", Color.White, Color.Black);
}
@@ -635,6 +672,7 @@ namespace Barotrauma
public void ReceiveTextInput(string input)
{
if (Readonly) { return; }
if (selectedCharacters > 0)
{
RemoveSelectedText();
@@ -658,7 +696,7 @@ namespace Barotrauma
switch (command)
{
case '\b': //backspace
case '\b' when !Readonly: //backspace
if (PlayerInput.KeyDown(Keys.LeftControl) || PlayerInput.KeyDown(Keys.RightControl))
{
SetText(string.Empty, false);
@@ -680,7 +718,7 @@ namespace Barotrauma
case (char)0x3: // ctrl-c
CopySelectedText();
break;
case (char)0x16: // ctrl-v
case (char)0x16 when !Readonly: // ctrl-v
string text = GetCopiedText();
RemoveSelectedText();
if (SetText(Text.Insert(CaretIndex, text)))
@@ -691,12 +729,15 @@ namespace Barotrauma
break;
case (char)0x18: // ctrl-x
CopySelectedText();
if (!Readonly)
{
RemoveSelectedText();
}
break;
case (char)0x1: // ctrl-a
SelectAll();
break;
case (char)0x1A: // ctrl-z
case (char)0x1A when !Readonly: // ctrl-z
text = memento.Undo();
if (text != Text)
{
@@ -706,7 +747,7 @@ namespace Barotrauma
OnTextChanged?.Invoke(this, Text);
}
break;
case (char)0x12: // ctrl-r
case (char)0x12 when !Readonly: // ctrl-r
text = memento.Redo();
if (text != Text)
{
@@ -763,7 +804,7 @@ namespace Barotrauma
caretTimer = 0;
HandleSelection();
break;
case Keys.Delete:
case Keys.Delete when !Readonly:
if (selectedCharacters > 0)
{
RemoveSelectedText();
@@ -806,7 +847,7 @@ namespace Barotrauma
}
}
}
IEnumerable<GUITextBox> GetAndSortTextBoxes(GUIComponent parent) => parent.GetAllChildren().Select(c => c as GUITextBox).Where(t => t != null).OrderBy(t => t.Rect.Y).ThenBy(t => t.Rect.X);
IEnumerable<GUITextBox> GetAndSortTextBoxes(GUIComponent parent) => parent.GetAllChildren<GUITextBox>().OrderBy(t => t.Rect.Y).ThenBy(t => t.Rect.X);
GUITextBox SelectNextTextBox(GUIListBox listBox)
{
var textBoxes = GetAndSortTextBoxes(listBox.SelectedComponent);

View File

@@ -0,0 +1,224 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
namespace Barotrauma
{
public class GUITickBox : GUIComponent
{
private GUILayoutGroup layoutGroup;
private GUIFrame box;
private GUITextBlock text;
public delegate bool OnSelectedHandler(GUITickBox obj);
public OnSelectedHandler OnSelected;
public static int size = 20;
private GUIRadioButtonGroup radioButtonGroup;
public override bool Selected
{
get { return selected; }
set
{
if (value == selected) { return; }
if (radioButtonGroup != null && radioButtonGroup.SelectedRadioButton == this)
{
selected = true;
return;
}
selected = value;
State = selected ? ComponentState.Selected : ComponentState.None;
if (value && radioButtonGroup != null)
{
radioButtonGroup.SelectRadioButton(this);
}
OnSelected?.Invoke(this);
}
}
public override ComponentState State
{
get
{
return base.State;
}
set
{
base.State = value;
box.State = TextBlock.State = value;
}
}
public override bool Enabled
{
get
{
return enabled;
}
set
{
if (value == enabled) { return; }
enabled = box.Enabled = TextBlock.Enabled = value;
}
}
public Color TextColor
{
get { return text.TextColor; }
set { text.TextColor = value; }
}
/*public override Rectangle MouseRect
{
get
{
if (!CanBeFocused) return Rectangle.Empty;
Rectangle union = Rectangle.Union(box.Rect, TextBlock.Rect);
Vector2 textPos = TextBlock.Rect.Location.ToVector2() + TextBlock.TextPos + TextBlock.TextOffset;
Vector2 textSize = TextBlock.Font.MeasureString(TextBlock.Text);
union = Rectangle.Union(union, new Rectangle(textPos.ToPoint(), textSize.ToPoint()));
union = Rectangle.Union(union, Rect);
return ClampMouseRectToParent ? ClampRect(union) : union;
}
}*/
public override ScalableFont Font
{
get
{
return base.Font;
}
set
{
base.Font = value;
if (text != null) text.Font = value;
}
}
public GUIFrame Box
{
get { return box; }
}
public GUITextBlock TextBlock
{
get { return text; }
}
public override string ToolTip
{
get { return base.ToolTip; }
set
{
base.ToolTip = value;
box.ToolTip = value;
text.ToolTip = value;
}
}
public string Text
{
get { return text.Text; }
set { text.Text = value; }
}
public GUITickBox(RectTransform rectT, string label, ScalableFont font = null, string style = "") : base(null, rectT)
{
CanBeFocused = true;
HoverCursor = CursorState.Hand;
layoutGroup = new GUILayoutGroup(new RectTransform(Vector2.One, rectT), true);
box = new GUIFrame(new RectTransform(Vector2.One, layoutGroup.RectTransform, Anchor.CenterLeft, scaleBasis: ScaleBasis.BothHeight)
{
IsFixedSize = true
}, string.Empty, Color.DarkGray)
{
HoverColor = Color.Gray,
SelectedColor = Color.DarkGray,
CanBeFocused = false
};
GUI.Style.Apply(box, style == "" ? "GUITickBox" : style);
if (box.RectTransform.MinSize.Y > 0)
{
RectTransform.MinSize = box.RectTransform.MinSize;
RectTransform.MaxSize = box.RectTransform.MaxSize;
RectTransform.Resize(new Point(RectTransform.NonScaledSize.X, RectTransform.MinSize.Y));
box.RectTransform.MinSize = new Point(box.RectTransform.MinSize.Y);
box.RectTransform.Resize(box.RectTransform.MinSize);
}
Vector2 textBlockScale = new Vector2((float)(Rect.Width - Rect.Height) / (float)Math.Max(Rect.Width, 1.0), 1.0f);
text = new GUITextBlock(new RectTransform(textBlockScale, layoutGroup.RectTransform, Anchor.CenterLeft), label, font: font, textAlignment: Alignment.CenterLeft)
{
CanBeFocused = false
};
GUI.Style.Apply(text, "GUITextBlock", this);
Enabled = true;
ResizeBox();
rectT.ScaleChanged += ResizeBox;
rectT.SizeChanged += ResizeBox;
}
public void SetRadioButtonGroup(GUIRadioButtonGroup rbg)
{
radioButtonGroup = rbg;
}
private void ResizeBox()
{
Vector2 textBlockScale = new Vector2(Math.Max(Rect.Width - box.Rect.Width, 0.0f) / Math.Max(Rect.Width, 1.0f), 1.0f);
text.RectTransform.RelativeSize = textBlockScale;
box.RectTransform.MinSize = new Point(Rect.Height);
box.RectTransform.Resize(box.RectTransform.MinSize);
text.SetTextPos();
}
protected override void Update(float deltaTime)
{
if (!Visible) { return; }
base.Update(deltaTime);
if (GUI.MouseOn == this && Enabled)
{
State = Selected ?
ComponentState.HoverSelected :
ComponentState.Hover;
if (PlayerInput.PrimaryMouseButtonHeld())
{
State = ComponentState.Selected;
}
if (PlayerInput.PrimaryMouseButtonClicked())
{
if (radioButtonGroup == null)
{
Selected = !Selected;
}
else if (!selected)
{
Selected = true;
}
}
}
else if (selected)
{
State = ComponentState.Selected;
}
else
{
State = ComponentState.None;
}
}
}
}

View File

@@ -0,0 +1,190 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
namespace Barotrauma
{
static class HUDLayoutSettings
{
public static bool DebugDraw;
private static int inventoryTopY;
public static int InventoryTopY
{
get { return inventoryTopY; }
set
{
if (value == inventoryTopY) return;
inventoryTopY = value;
CreateAreas();
}
}
public static Rectangle ButtonAreaTop
{
get; private set;
}
public static Rectangle MessageAreaTop
{
get; private set;
}
public static Rectangle CrewArea
{
get; private set;
}
public static Rectangle ChatBoxArea
{
get; private set;
}
public static Rectangle ObjectiveAnchor
{
get; private set;
}
public static Rectangle InventoryAreaLower
{
get; private set;
}
/*public static Rectangle HealthBarAreaRight
{
get; private set;
}*/
public static Rectangle HealthBarArea
{
get; private set;
}
public static Rectangle BottomRightInfoArea
{
get; private set;
}
public static Rectangle AfflictionAreaLeft
{
get; private set;
}
public static Rectangle HealthWindowAreaLeft
{
get; private set;
}
public static Rectangle PortraitArea
{
get; private set;
}
public static int Padding
{
get; private set;
}
static HUDLayoutSettings()
{
if (GameMain.Instance != null)
{
GameMain.Instance.OnResolutionChanged += CreateAreas;
GameMain.Config.OnHUDScaleChanged += CreateAreas;
CreateAreas();
CharacterInfo.Init();
}
}
public static RectTransform ToRectTransform(Rectangle rect, RectTransform parent)
{
return new RectTransform(new Vector2(rect.Width / (float)GameMain.GraphicsWidth, rect.Height / (float)GameMain.GraphicsHeight), parent)
{
RelativeOffset = new Vector2(rect.X / (float)GameMain.GraphicsWidth, rect.Y / (float)GameMain.GraphicsHeight)
};
}
public static void CreateAreas()
{
Padding = (int)(11 * GUI.Scale);
if (inventoryTopY == 0) { inventoryTopY = GameMain.GraphicsHeight - 30; }
//slice from the top of the screen for misc buttons (info, end round, server controls)
ButtonAreaTop = new Rectangle(Padding, Padding, GameMain.GraphicsWidth - Padding * 2, (int)(50 * GUI.Scale));
int infoAreaWidth = (int)(142 * GUI.Scale);
int infoAreaHeight = (int)(98 * GUI.Scale);
int portraitSize = (int)(infoAreaHeight * 0.95f);
BottomRightInfoArea = new Rectangle(GameMain.GraphicsWidth - Padding * 2 - infoAreaWidth, GameMain.GraphicsHeight - Padding * 2 - infoAreaHeight, infoAreaWidth, infoAreaHeight);
PortraitArea = new Rectangle(GameMain.GraphicsWidth - portraitSize, BottomRightInfoArea.Bottom - portraitSize + Padding / 2, portraitSize, portraitSize);
//horizontal slices at the corners of the screen for health bar and affliction icons
int afflictionAreaHeight = (int)(50 * GUI.Scale);
int healthBarWidth = BottomRightInfoArea.Width + CharacterInventory.SlotSize.X + CharacterInventory.Spacing * 2 + CharacterInventory.HideButtonWidth;
int healthBarHeight = (int)(50f * GUI.Scale);
HealthBarArea = new Rectangle(BottomRightInfoArea.X - (healthBarWidth - BottomRightInfoArea.Width) + (int)(2 * GUI.Scale), BottomRightInfoArea.Y - healthBarHeight + (int)(10 * GUI.Scale), healthBarWidth, healthBarHeight);
AfflictionAreaLeft = new Rectangle(HealthBarArea.X, HealthBarArea.Y - Padding - afflictionAreaHeight, HealthBarArea.Width, afflictionAreaHeight);
//HealthBarAreaRight = new Rectangle(Padding, GameMain.GraphicsHeight - healthBarHeight - Padding, healthBarWidth, healthBarHeight);
/*if (HealthBarAreaRight.Y + healthBarHeight * 0.75f < PortraitArea.Y)
{
HealthBarAreaRight = new Rectangle(GameMain.GraphicsWidth - Padding - healthBarWidth, HealthBarAreaRight.Y, HealthBarAreaRight.Width, HealthBarAreaRight.Height);
}*/
//AfflictionAreaRight = new Rectangle(HealthBarAreaRight.X, HealthBarAreaRight.Y + healthBarHeight + Padding, healthBarWidth, afflictionAreaHeight);
int messageAreaWidth = GameMain.GraphicsWidth / 3;
MessageAreaTop = new Rectangle((GameMain.GraphicsWidth - messageAreaWidth) / 2, ButtonAreaTop.Bottom, messageAreaWidth, ButtonAreaTop.Height);
bool isFourByThree = GUI.IsFourByThree();
int chatBoxWidth = !isFourByThree ? (int)(475 * GUI.Scale) : (int)(375 * GUI.Scale);
int chatBoxHeight = (int)Math.Max(GameMain.GraphicsHeight * 0.25f, 150);
ChatBoxArea = new Rectangle(Padding, GameMain.GraphicsHeight - Padding - chatBoxHeight, chatBoxWidth, chatBoxHeight);
int objectiveAnchorWidth = (int)(250 * GUI.Scale);
int objectiveAnchorOffsetY = (int)(150 * GUI.Scale);
ObjectiveAnchor = new Rectangle(Padding, ChatBoxArea.Y - objectiveAnchorOffsetY, objectiveAnchorWidth, 0);
CrewArea = new Rectangle(Padding, Padding, (int)Math.Max(400 * GUI.Scale, 220), ObjectiveAnchor.Top - Padding * 2);
InventoryAreaLower = new Rectangle(Padding, inventoryTopY, GameMain.GraphicsWidth - Padding * 2, GameMain.GraphicsHeight - inventoryTopY);
int healthWindowWidth = (int)(GameMain.GraphicsWidth * 0.5f);
int healthWindowHeight = (int)(GameMain.GraphicsWidth * 0.5f * 0.65f);
int healthWindowX = GameMain.GraphicsWidth / 2 - healthWindowWidth / 2;
int healthWindowY = GameMain.GraphicsHeight / 2 - healthWindowHeight / 2;
HealthWindowAreaLeft = new Rectangle(healthWindowX, healthWindowY, healthWindowWidth, healthWindowHeight);
}
public static void Draw(SpriteBatch spriteBatch)
{
GUI.DrawRectangle(spriteBatch, ButtonAreaTop, Color.White * 0.5f);
GUI.DrawRectangle(spriteBatch, MessageAreaTop, GUI.Style.Orange * 0.5f);
GUI.DrawRectangle(spriteBatch, CrewArea, Color.Blue * 0.5f);
GUI.DrawRectangle(spriteBatch, ChatBoxArea, Color.Cyan * 0.5f);
GUI.DrawRectangle(spriteBatch, HealthBarArea, Color.Red * 0.5f);
GUI.DrawRectangle(spriteBatch, AfflictionAreaLeft, Color.Red * 0.5f);
GUI.DrawRectangle(spriteBatch, InventoryAreaLower, Color.Yellow * 0.5f);
GUI.DrawRectangle(spriteBatch, HealthWindowAreaLeft, Color.Red * 0.5f);
GUI.DrawRectangle(spriteBatch, BottomRightInfoArea, Color.Green * 0.5f);
}
}
public static class HUD
{
public static bool CloseHUD(Rectangle rect)
{
// Always close when hitting escape
if (PlayerInput.KeyHit(Microsoft.Xna.Framework.Input.Keys.Escape)) { return true; }
//don't close when the cursor is on a UI element
if (GUI.MouseOn != null) { return false; }
//don't close when hovering over an inventory element
if (Inventory.IsMouseOnInventory()) { return false; }
bool input = PlayerInput.PrimaryMouseButtonDown() || PlayerInput.SecondaryMouseButtonClicked();
return input && !rect.Contains(PlayerInput.MousePosition);
}
}
}

View File

@@ -0,0 +1,387 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using System;
using System.Collections.Generic;
using System.Xml.Linq;
using Barotrauma.Media;
using System.Linq;
using Barotrauma.Extensions;
namespace Barotrauma
{
class LoadingScreen
{
private readonly Texture2D defaultBackgroundTexture, overlay;
private readonly SpriteSheet decorativeGraph, decorativeMap;
private Texture2D currentBackgroundTexture;
private Sprite noiseSprite;
private string randText = "";
private Sprite languageSelectionCursor;
private ScalableFont languageSelectionFont, languageSelectionFontCJK;
private Video currSplashScreen;
private DateTime videoStartTime;
public struct PendingSplashScreen
{
public string Filename;
public float Gain;
public PendingSplashScreen(string filename, float gain)
{
Filename = filename;
Gain = gain;
}
}
private Queue<PendingSplashScreen> pendingSplashScreens = new Queue<PendingSplashScreen>();
/// <summary>
/// Triplet.first = filepath, Triplet.second = resolution, Triplet.third = audio gain
/// </summary>
public Queue<PendingSplashScreen> PendingSplashScreens
{
get
{
lock (loadMutex)
{
return pendingSplashScreens;
}
}
set
{
lock (loadMutex)
{
pendingSplashScreens = value;
}
}
}
public bool PlayingSplashScreen
{
get
{
lock (loadMutex)
{
return currSplashScreen != null || pendingSplashScreens.Count > 0;
}
}
}
private string selectedTip;
private readonly object loadMutex = new object();
private float? loadState;
public float? LoadState
{
get
{
lock (loadMutex)
{
return loadState;
}
}
set
{
lock (loadMutex)
{
loadState = value;
DrawLoadingText = true;
}
}
}
public bool DrawLoadingText
{
get;
set;
}
public bool WaitForLanguageSelection
{
get;
set;
}
public LoadingScreen(GraphicsDevice graphics)
{
defaultBackgroundTexture = TextureLoader.FromFile("Content/Map/LocationPortraits/AlienRuins.png");
decorativeMap = new SpriteSheet("Content/Map/MapHUD.png", 6, 5, Vector2.Zero, sourceRect: new Rectangle(0, 0, 2048, 640));
decorativeGraph = new SpriteSheet("Content/Map/MapHUD.png", 4, 10, Vector2.Zero, sourceRect: new Rectangle(1025, 1259, 1024, 732));
overlay = TextureLoader.FromFile("Content/UI/LoadingScreenOverlay.png");
noiseSprite = new Sprite("Content/UI/noise.png", Vector2.Zero);
DrawLoadingText = true;
selectedTip = TextManager.Get("LoadingScreenTip", true);
}
public void Draw(SpriteBatch spriteBatch, GraphicsDevice graphics, float deltaTime)
{
if (GameMain.Config.EnableSplashScreen)
{
try
{
DrawSplashScreen(spriteBatch, graphics);
if (currSplashScreen != null || PendingSplashScreens.Count > 0) { return; }
}
catch (Exception e)
{
DebugConsole.ThrowError("Playing splash screen video failed", e);
GameMain.Config.EnableSplashScreen = false;
}
}
var titleStyle = GUI.Style?.GetComponentStyle("TitleText");
Sprite titleSprite = null;
if (!WaitForLanguageSelection && titleStyle != null && titleStyle.Sprites.ContainsKey(GUIComponent.ComponentState.None))
{
titleSprite = titleStyle.Sprites[GUIComponent.ComponentState.None].First()?.Sprite;
}
drawn = true;
currentBackgroundTexture ??= defaultBackgroundTexture;
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, samplerState: GUI.SamplerState);
float scale = (GameMain.GraphicsWidth / (float)currentBackgroundTexture.Width) * 1.2f;
float paddingX = currentBackgroundTexture.Width * scale - GameMain.GraphicsWidth;
float paddingY = currentBackgroundTexture.Height * scale - GameMain.GraphicsHeight;
double noiseT = (Timing.TotalTime * 0.02f);
Vector2 pos = new Vector2((float)PerlinNoise.CalculatePerlin(noiseT, noiseT, 0) - 0.5f, (float)PerlinNoise.CalculatePerlin(noiseT, noiseT, 0.5f) - 0.5f);
pos = new Vector2(pos.X * paddingX, pos.Y * paddingY);
spriteBatch.Draw(currentBackgroundTexture,
new Vector2(GameMain.GraphicsWidth, GameMain.GraphicsHeight) / 2 + pos,
null, Color.White, 0.0f, new Vector2(currentBackgroundTexture.Width / 2, currentBackgroundTexture.Height / 2),
scale, SpriteEffects.None, 0.0f);
spriteBatch.Draw(overlay, new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight), null, Color.White, 0.0f, Vector2.Zero, SpriteEffects.None, 0.0f);
float noiseStrength = (float)PerlinNoise.CalculatePerlin(noiseT, noiseT, 0);
float noiseScale = (float)PerlinNoise.CalculatePerlin(noiseT * 5.0f, noiseT * 2.0f, 0) * 4.0f;
noiseSprite.DrawTiled(spriteBatch, Vector2.Zero, new Vector2(GameMain.GraphicsWidth, GameMain.GraphicsHeight),
startOffset: new Point(Rand.Range(0, noiseSprite.SourceRect.Width), Rand.Range(0, noiseSprite.SourceRect.Height)),
color: Color.White * noiseStrength * 0.1f,
textureScale: Vector2.One * noiseScale);
titleSprite?.Draw(spriteBatch, new Vector2(GameMain.GraphicsWidth * 0.05f, GameMain.GraphicsHeight * 0.125f),
Color.White, origin: new Vector2(0.0f, titleSprite.SourceRect.Height / 2.0f),
scale: GameMain.GraphicsHeight / 2000.0f);
if (WaitForLanguageSelection)
{
DrawLanguageSelectionPrompt(spriteBatch, graphics);
}
else if (DrawLoadingText)
{
if (TextManager.Initialized)
{
string loadText;
if (LoadState == 100.0f)
{
#if DEBUG
if (GameMain.Config.AutomaticQuickStartEnabled && GameMain.FirstLoad)
{
loadText = "QUICKSTARTING ...";
}
else
{
#endif
loadText = TextManager.Get("PressAnyKey");
#if DEBUG
}
#endif
}
else
{
loadText = TextManager.Get("Loading");
if (LoadState != null)
{
loadText += " " + (int)LoadState + " %";
}
}
if (GUI.LargeFont != null)
{
GUI.LargeFont.DrawString(spriteBatch, loadText.ToUpper(),
new Vector2(GameMain.GraphicsWidth / 2.0f - GUI.LargeFont.MeasureString(loadText.ToUpper()).X / 2.0f, GameMain.GraphicsHeight * 0.75f),
Color.White);
}
}
if (GUI.Font != null && selectedTip != null)
{
string wrappedTip = ToolBox.WrapText(selectedTip, GameMain.GraphicsWidth * 0.5f, GUI.Font);
string[] lines = wrappedTip.Split('\n');
float lineHeight = GUI.Font.MeasureString(selectedTip).Y;
for (int i = 0; i < lines.Length; i++)
{
GUI.Font.DrawString(spriteBatch, lines[i],
new Vector2((int)(GameMain.GraphicsWidth / 2.0f - GUI.Font.MeasureString(lines[i]).X / 2.0f), (int)(GameMain.GraphicsHeight * 0.8f + i * lineHeight)), Color.White);
}
}
}
spriteBatch.End();
spriteBatch.Begin(blendState: BlendState.Additive);
Vector2 decorativeScale = new Vector2(GameMain.GraphicsHeight / 1080.0f);
float noiseVal = (float)PerlinNoise.CalculatePerlin(Timing.TotalTime * 0.25f, Timing.TotalTime * 0.5f, 0);
decorativeGraph.Draw(spriteBatch, (int)(decorativeGraph.FrameCount * noiseVal),
new Vector2(GameMain.GraphicsWidth * 0.001f, GameMain.GraphicsHeight * 0.24f),
Color.White, Vector2.Zero, 0.0f, decorativeScale, SpriteEffects.FlipVertically);
decorativeMap.Draw(spriteBatch, (int)(decorativeMap.FrameCount * noiseVal),
new Vector2(GameMain.GraphicsWidth * 0.99f, GameMain.GraphicsHeight * 0.66f),
Color.White, decorativeMap.FrameSize.ToVector2(), 0.0f, decorativeScale);
if (noiseVal < 0.2f)
{
//SCP-CB reference
randText = (new string[] { "NIL", "black white gray", "Sometimes we would have had time to scream", "e8m106]af", "NO" }).GetRandom();
}
else if (noiseVal < 0.3f)
{
randText = ToolBox.RandomSeed(9);
}
else if (noiseVal < 0.5f)
{
randText =
Rand.Int(100).ToString().PadLeft(2, '0') + " " +
Rand.Int(100).ToString().PadLeft(2, '0') + " " +
Rand.Int(100).ToString().PadLeft(2, '0') + " " +
Rand.Int(100).ToString().PadLeft(2, '0');
}
GUI.LargeFont?.DrawString(spriteBatch, randText,
new Vector2(GameMain.GraphicsWidth - decorativeMap.FrameSize.X * decorativeScale.X * 0.8f, GameMain.GraphicsHeight * 0.57f),
Color.White * (1.0f - noiseVal));
spriteBatch.End();
}
private void DrawLanguageSelectionPrompt(SpriteBatch spriteBatch, GraphicsDevice graphicsDevice)
{
if (languageSelectionFont == null)
{
languageSelectionFont = new ScalableFont("Content/Fonts/NotoSans/NotoSans-Bold.ttf",
(uint)(30 * (GameMain.GraphicsHeight / 1080.0f)), graphicsDevice);
}
if (languageSelectionFontCJK == null)
{
languageSelectionFontCJK = new ScalableFont("Content/Fonts/NotoSans/NotoSansCJKsc-Bold.otf",
(uint)(30 * (GameMain.GraphicsHeight / 1080.0f)), graphicsDevice, dynamicLoading: true);
}
if (languageSelectionCursor == null)
{
languageSelectionCursor = new Sprite("Content/UI/cursor.png", Vector2.Zero);
}
Vector2 textPos = new Vector2(GameMain.GraphicsWidth / 2, GameMain.GraphicsHeight * 0.3f);
Vector2 textSpacing = new Vector2(0.0f, (GameMain.GraphicsHeight * 0.5f) / TextManager.AvailableLanguages.Count());
foreach (string language in TextManager.AvailableLanguages)
{
string localizedLanguageName = TextManager.GetTranslatedLanguageName(language);
var font = TextManager.IsCJK(localizedLanguageName) ? languageSelectionFontCJK : languageSelectionFont;
Vector2 textSize = font.MeasureString(localizedLanguageName);
bool hover =
Math.Abs(PlayerInput.MousePosition.X - textPos.X) < textSize.X / 2 &&
Math.Abs(PlayerInput.MousePosition.Y - textPos.Y) < textSpacing.Y / 2;
font.DrawString(spriteBatch, localizedLanguageName, textPos - textSize / 2,
hover ? Color.White : Color.White * 0.6f);
if (hover && PlayerInput.PrimaryMouseButtonClicked())
{
GameMain.Config.Language = language;
//reload tip in the selected language
selectedTip = TextManager.Get("LoadingScreenTip", true);
GameMain.Config.SetDefaultBindings(legacy: false);
GameMain.Config.CheckBindings(useDefaults: true);
WaitForLanguageSelection = false;
languageSelectionFont?.Dispose(); languageSelectionFont = null;
languageSelectionFontCJK?.Dispose(); languageSelectionFontCJK = null;
break;
}
textPos += textSpacing;
}
languageSelectionCursor.Draw(spriteBatch, PlayerInput.LatestMousePosition, scale: 0.5f);
}
private void DrawSplashScreen(SpriteBatch spriteBatch, GraphicsDevice graphics)
{
if (currSplashScreen == null && PendingSplashScreens.Count == 0) { return; }
if (currSplashScreen == null)
{
var newSplashScreen = PendingSplashScreens.Dequeue();
string fileName = newSplashScreen.Filename;
try
{
currSplashScreen = Video.Load(graphics, GameMain.SoundManager, fileName);
currSplashScreen.AudioGain = newSplashScreen.Gain;
videoStartTime = DateTime.Now;
}
catch (Exception e)
{
GameMain.Config.EnableSplashScreen = false;
DebugConsole.ThrowError("Playing the splash screen \"" + fileName + "\" failed.", e);
PendingSplashScreens.Clear();
currSplashScreen = null;
}
}
if (currSplashScreen.IsPlaying)
{
spriteBatch.Begin();
spriteBatch.Draw(currSplashScreen.GetTexture(), new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight), Color.White);
spriteBatch.End();
if (DateTime.Now > videoStartTime + new TimeSpan(0, 0, 0, 0, milliseconds: 500) && GameMain.WindowActive && (PlayerInput.KeyHit(Keys.Space) || PlayerInput.KeyHit(Keys.Enter) || PlayerInput.PrimaryMouseButtonDown()))
{
currSplashScreen.Dispose(); currSplashScreen = null;
}
}
else if (DateTime.Now > videoStartTime + new TimeSpan(0, 0, 0, 0, milliseconds: 1500))
{
currSplashScreen.Dispose(); currSplashScreen = null;
}
}
bool drawn;
public IEnumerable<object> DoLoading(IEnumerable<object> loader)
{
drawn = false;
LoadState = null;
selectedTip = TextManager.Get("LoadingScreenTip", true);
currentBackgroundTexture = LocationType.List.GetRandom()?.GetPortrait(Rand.Int(int.MaxValue))?.Texture;
while (!drawn)
{
yield return CoroutineStatus.Running;
}
CoroutineManager.StartCoroutine(loader);
yield return CoroutineStatus.Running;
while (CoroutineManager.IsCoroutineRunning(loader.ToString()))
{
yield return CoroutineStatus.Running;
}
LoadState = 100.0f;
yield return CoroutineStatus.Success;
}
}
}

View File

@@ -35,7 +35,9 @@ namespace Barotrauma
Parent = new GUIFrame(rectT, null, Color);
EditorBox = new GUIListBox(new RectTransform(Vector2.One * 0.98f, rectT, Anchor.Center), color: Color.Black, style: null)
{
Spacing = 10
Spacing = 10,
AutoHideScrollBar = true,
KeepSpaceForScrollBar = true
};
return EditorBox;
}

View File

@@ -23,7 +23,9 @@ namespace Barotrauma
public enum ScaleBasis
{
Normal, BothWidth, BothHeight
Normal,
BothWidth, BothHeight,
Smallest, Largest
}
public class RectTransform
@@ -50,13 +52,13 @@ namespace Barotrauma
{
parent.children.Add(this);
RecalculateAll(false, true, true);
ParentChanged?.Invoke(parent);
Parent.ChildrenChanged?.Invoke(this);
}
ParentChanged?.Invoke(parent);
}
}
private List<RectTransform> children = new List<RectTransform>();
private readonly List<RectTransform> children = new List<RectTransform>();
public IEnumerable<RectTransform> Children => children;
public int CountChildren => children.Count;
@@ -291,7 +293,16 @@ namespace Barotrauma
}
}
private ScaleBasis scaleBasis;
private ScaleBasis _scaleBasis;
public ScaleBasis ScaleBasis
{
get { return _scaleBasis; }
set
{
_scaleBasis = value;
RecalculateAbsoluteSize();
}
}
public bool IsLastChild
{
@@ -330,7 +341,7 @@ namespace Barotrauma
public RectTransform(Vector2 relativeSize, RectTransform parent, Anchor anchor = Anchor.TopLeft, Pivot? pivot = null, Point? minSize = null, Point? maxSize = null, ScaleBasis scaleBasis = ScaleBasis.Normal)
{
Init(parent, anchor, pivot);
this.scaleBasis = scaleBasis;
_scaleBasis = scaleBasis;
this.relativeSize = relativeSize;
this.minSize = minSize;
this.maxSize = maxSize;
@@ -342,19 +353,23 @@ namespace Barotrauma
}
/// <summary>
/// By default, elements defined with an absolute size (in pixels), will be treated as fixed sized.
/// This can be changed by setting IsFixedSize to false.
/// By default, elements defined with an absolute size (in pixels) will scale with the parent.
/// This can be changed by setting IsFixedSize to true.
/// </summary>
public RectTransform(Point absoluteSize, RectTransform parent = null, Anchor anchor = Anchor.TopLeft, Pivot? pivot = null)
public RectTransform(Point absoluteSize, RectTransform parent = null, Anchor anchor = Anchor.TopLeft, Pivot? pivot = null, ScaleBasis scaleBasis = ScaleBasis.Normal, bool isFixedSize = false)
{
Init(parent, anchor, pivot);
this.scaleBasis = ScaleBasis.Normal;
_scaleBasis = scaleBasis;
this.nonScaledSize = absoluteSize;
RecalculateScale();
RecalculateRelativeSize();
if (scaleBasis != ScaleBasis.Normal)
{
RecalculateAbsoluteSize();
}
RecalculateAnchorPoint();
RecalculatePivotOffset();
IsFixedSize = true;
IsFixedSize = isFixedSize;
parent?.ChildrenChanged?.Invoke(this);
}
@@ -364,13 +379,24 @@ namespace Barotrauma
Enum.TryParse(element.GetAttributeString("pivot", anchor.ToString()), out Pivot pivot);
Point? minSize = null, maxSize = null;
if (element.Attribute("minsize") != null) minSize = element.GetAttributePoint("minsize", Point.Zero);
//if (element.Attribute("maxsize") != null) maxSize = element.GetAttributePoint("maxsize", new Point(1000, 1000));
ScaleBasis scaleBasis = ScaleBasis.Normal;
if (element.Attribute("minsize") != null)
{
minSize = element.GetAttributePoint("minsize", Point.Zero);
}
if (element.Attribute("maxsize") != null)
{
maxSize = element.GetAttributePoint("maxsize", new Point(1000, 1000));
}
string sb = element.GetAttributeString("scalebasis", null);
if (sb != null)
{
Enum.TryParse(sb, ignoreCase: true, out scaleBasis);
}
RectTransform rectTransform;
if (element.Attribute("absolutesize") != null)
{
rectTransform = new RectTransform(element.GetAttributePoint("absolutesize", new Point(1000, 1000)), parent, anchor, pivot)
rectTransform = new RectTransform(element.GetAttributePoint("absolutesize", new Point(1000, 1000)), parent, anchor, pivot, scaleBasis)
{
minSize = minSize,
maxSize = maxSize
@@ -378,7 +404,7 @@ namespace Barotrauma
}
else
{
rectTransform = new RectTransform(element.GetAttributeVector2("relativesize", Vector2.One), parent, anchor, pivot, minSize, maxSize);
rectTransform = new RectTransform(element.GetAttributeVector2("relativesize", Vector2.One), parent, anchor, pivot, minSize, maxSize, scaleBasis);
}
rectTransform.RelativeOffset = element.GetAttributeVector2("relativeoffset", Vector2.Zero);
rectTransform.AbsoluteOffset = element.GetAttributePoint("absoluteoffset", Point.Zero);
@@ -426,15 +452,37 @@ namespace Barotrauma
protected void RecalculateAbsoluteSize()
{
Point size = NonScaledParentRect.Size;
if (scaleBasis == ScaleBasis.BothWidth)
switch (ScaleBasis)
{
case ScaleBasis.BothWidth:
size.Y = size.X;
break;
case ScaleBasis.BothHeight:
size.X = size.Y;
break;
case ScaleBasis.Smallest:
if (size.X < size.Y)
{
size.Y = size.X;
}
else if (scaleBasis == ScaleBasis.BothHeight)
else
{
size.X = size.Y;
}
nonScaledSize = size.Multiply(RelativeSize).Clamp(MinSize, MaxSize);
break;
case ScaleBasis.Largest:
if (size.X > size.Y)
{
size.Y = size.X;
}
else
{
size.X = size.Y;
}
break;
}
size = size.Multiply(RelativeSize);
nonScaledSize = size.Clamp(MinSize, MaxSize);
recalculateRect = true;
SizeChanged?.Invoke();
}
@@ -464,7 +512,7 @@ namespace Barotrauma
}
}
private bool RemoveFromHierarchy(bool displayErrors = true, bool recalculate = true)
private bool RemoveFromHierarchy(bool displayErrors = true)
{
if (Parent == null)
{
@@ -506,7 +554,7 @@ namespace Barotrauma
public void Resize(Point absoluteSize, bool resizeChildren = true)
{
nonScaledSize = absoluteSize;
nonScaledSize = absoluteSize.Clamp(MinSize, MaxSize);
RecalculateRelativeSize();
RecalculateAll(resize: false, scale: false, withChildren: false);
RecalculateChildren(resizeChildren, false);
@@ -573,7 +621,7 @@ namespace Barotrauma
/// </summary>
public IEnumerable<RectTransform> GetAllChildren()
{
return children.SelectManyRecursive(c => c.children);
return children.Concat(children.SelectManyRecursive(c => c.children));
}
public int GetChildIndex(RectTransform rectT)
@@ -599,7 +647,7 @@ namespace Barotrauma
public void SortChildren(Comparison<RectTransform> comparison)
{
children.Sort(comparison);
RecalculateAll(false, true, true);
RecalculateAll(false, false, true);
Parent.ChildrenChanged?.Invoke(this);
}
@@ -650,6 +698,53 @@ namespace Barotrauma
children[i].GUIComponent.AddToGUIUpdateList(ignoreChildren, order);
}
}
public void MatchPivotToAnchor() => MatchPivotToAnchor(Anchor);
private Point? animTargetPos;
public Point AnimTargetPos
{
get { return animTargetPos ?? AbsoluteOffset; }
}
public void MoveOverTime(Point targetPos, float duration)
{
animTargetPos = targetPos;
CoroutineManager.StartCoroutine(DoMoveAnimation(targetPos, duration));
}
public void ScaleOverTime(Point targetSize, float duration)
{
CoroutineManager.StartCoroutine(DoScaleAnimation(targetSize, duration));
}
private IEnumerable<object> DoMoveAnimation(Point targetPos, float duration)
{
Vector2 startPos = AbsoluteOffset.ToVector2();
float t = 0.0f;
while (t < duration && duration > 0.0f)
{
t += CoroutineManager.DeltaTime;
AbsoluteOffset = Vector2.SmoothStep(startPos, targetPos.ToVector2(), t / duration).ToPoint();
yield return CoroutineStatus.Running;
}
AbsoluteOffset = targetPos;
animTargetPos = null;
yield return CoroutineStatus.Success;
}
private IEnumerable<object> DoScaleAnimation(Point targetSize, float duration)
{
Vector2 startSize = NonScaledSize.ToVector2();
float t = 0.0f;
while (t < duration && duration > 0.0f)
{
t += CoroutineManager.DeltaTime;
NonScaledSize = Vector2.SmoothStep(startSize, targetSize.ToVector2(), t / duration).ToPoint();
yield return CoroutineStatus.Running;
}
NonScaledSize = targetSize;
yield return CoroutineStatus.Success;
}
#endregion
#region Static methods

View File

@@ -0,0 +1,173 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Xml.Linq;
namespace Barotrauma
{
public class UISprite
{
public Sprite Sprite
{
get;
private set;
}
public bool Tile
{
get;
private set;
}
public bool Slice
{
get;
set;
}
public Rectangle[] Slices
{
get;
set;
}
public bool MaintainAspectRatio
{
get;
private set;
}
/// <summary>
/// How much the borders of a sliced sprite are allowed to scale
/// You may for example want to prevent a 1-pixel border from scaling down (and disappearing) on small resolutions
/// </summary>
private readonly float minBorderScale = 0.1f, maxBorderScale = 10.0f;
public bool CrossFadeIn { get; private set; } = true;
public bool CrossFadeOut { get; private set; } = true;
public TransitionMode TransitionMode { get; private set; }
public UISprite(XElement element)
{
Sprite = new Sprite(element);
MaintainAspectRatio = element.GetAttributeBool("maintainaspectratio", false);
Tile = element.GetAttributeBool("tile", true);
CrossFadeIn = element.GetAttributeBool("crossfadein", CrossFadeIn);
CrossFadeOut = element.GetAttributeBool("crossfadeout", CrossFadeOut);
string transitionMode = element.GetAttributeString("transition", string.Empty);
if (Enum.TryParse(transitionMode, ignoreCase: true, out TransitionMode transition))
{
TransitionMode = transition;
}
Vector4 sliceVec = element.GetAttributeVector4("slice", Vector4.Zero);
if (sliceVec != Vector4.Zero)
{
minBorderScale = element.GetAttributeFloat("minborderscale", 0.1f);
maxBorderScale = element.GetAttributeFloat("minborderscale", 10.0f);
Rectangle slice = new Rectangle((int)sliceVec.X, (int)sliceVec.Y, (int)(sliceVec.Z - sliceVec.X), (int)(sliceVec.W - sliceVec.Y));
Slice = true;
Slices = new Rectangle[9];
//top-left
Slices[0] = new Rectangle(Sprite.SourceRect.Location, slice.Location - Sprite.SourceRect.Location);
//top-mid
Slices[1] = new Rectangle(slice.Location.X, Slices[0].Y, slice.Width, Slices[0].Height);
//top-right
Slices[2] = new Rectangle(slice.Right, Slices[0].Y, Sprite.SourceRect.Right - slice.Right, Slices[0].Height);
//mid-left
Slices[3] = new Rectangle(Slices[0].X, slice.Y, Slices[0].Width, slice.Height);
//center
Slices[4] = slice;
//mid-right
Slices[5] = new Rectangle(Slices[2].X, slice.Y, Slices[2].Width, slice.Height);
//bottom-left
Slices[6] = new Rectangle(Slices[0].X, slice.Bottom, Slices[0].Width, Sprite.SourceRect.Bottom - slice.Bottom);
//bottom-mid
Slices[7] = new Rectangle(Slices[1].X, slice.Bottom, Slices[1].Width, Sprite.SourceRect.Bottom - slice.Bottom);
//bottom-right
Slices[8] = new Rectangle(Slices[2].X, slice.Bottom, Slices[2].Width, Sprite.SourceRect.Bottom - slice.Bottom);
}
}
/// <summary>
/// Get the scale of the sliced sprite's borders when it's draw inside an area of a specific size
/// </summary>
public float GetSliceBorderScale(Point drawSize)
{
if (!Slice) { return 1.0f; }
Vector2 scale = new Vector2(
MathHelper.Clamp((float)drawSize.X / (Slices[0].Height + Slices[6].Height), 0, 1),
MathHelper.Clamp((float)drawSize.Y / (Slices[0].Width + Slices[2].Width), 0, 1));
return MathHelper.Clamp(Math.Min(Math.Min(scale.X, scale.Y), GUI.SlicedSpriteScale), minBorderScale, maxBorderScale);
}
public void Draw(SpriteBatch spriteBatch, Rectangle rect, Color color, SpriteEffects spriteEffects = SpriteEffects.None)
{
if (Sprite.Texture == null)
{
GUI.DrawRectangle(spriteBatch, rect, Color.Magenta);
return;
}
if (Slice)
{
Vector2 pos = new Vector2(rect.X, rect.Y);
float scale = GetSliceBorderScale(rect.Size);
int centerHeight = rect.Height - (int)((Slices[0].Height + Slices[6].Height) * scale);
int centerWidth = rect.Width - (int)((Slices[0].Width + Slices[2].Width) * scale);
for (int x = 0; x < 3; x++)
{
int width = (int)(x == 1 ? centerWidth : Slices[x].Width * scale);
if (width <= 0) { continue; }
for (int y = 0; y < 3; y++)
{
int height = (int)(y == 1 ? centerHeight : Slices[x + y * 3].Height * scale);
if (height <= 0) { continue; }
spriteBatch.Draw(Sprite.Texture,
new Rectangle((int)pos.X, (int)pos.Y, width, height),
Slices[x + y * 3],
color);
pos.Y += height;
}
pos.X += width;
pos.Y = rect.Y;
}
}
else if (Tile)
{
Vector2 startPos = new Vector2(rect.X, rect.Y);
Sprite.DrawTiled(spriteBatch, startPos, new Vector2(rect.Width, rect.Height), null, color);
}
else
{
if (MaintainAspectRatio)
{
float scale = Math.Min((float)rect.Width / Sprite.SourceRect.Width, (float)rect.Height / Sprite.SourceRect.Height);
spriteBatch.Draw(Sprite.Texture, rect.Center.ToVector2(),
Sprite.SourceRect,
color,
rotation: 0.0f,
origin: Sprite.size / 2.0f,
scale: scale,
effects: spriteEffects, layerDepth: 0.0f);
}
else
{
spriteBatch.Draw(Sprite.Texture, rect, Sprite.SourceRect, color, 0, Vector2.Zero, spriteEffects, 0);
}
}
}
}
}

View File

@@ -20,7 +20,7 @@ namespace Barotrauma
private GUICustomComponent videoView;
private GUIButton okButton;
private Color backgroundColor = new Color(0f, 0f, 0f, 1f);
private Color backgroundColor = new Color(0f, 0f, 0f, 0.8f);
private Action callbackOnStop;
private Point scaledVideoResolution;
@@ -62,8 +62,8 @@ namespace Barotrauma
int width = scaledVideoResolution.X;
int height = scaledVideoResolution.Y;
background = new GUIFrame(new RectTransform(Point.Zero, GUI.Canvas, Anchor.Center), "InnerFrame", backgroundColor);
videoFrame = new GUIFrame(new RectTransform(Point.Zero, background.RectTransform, Anchor.Center, Pivot.Center), "SonarFrame");
background = new GUIFrame(new RectTransform(Point.Zero, GUI.Canvas, Anchor.Center), style: null, color: backgroundColor);
videoFrame = new GUIFrame(new RectTransform(Point.Zero, background.RectTransform, Anchor.Center, Pivot.Center), style: "InnerFrame");
if (useTextOnRightSide)
{
@@ -75,13 +75,13 @@ namespace Barotrauma
}
videoView = new GUICustomComponent(new RectTransform(Point.Zero, videoFrame.RectTransform, Anchor.Center), (spriteBatch, guiCustomComponent) => { DrawVideo(spriteBatch, guiCustomComponent.Rect); });
title = new GUITextBlock(new RectTransform(Point.Zero, textFrame.RectTransform, Anchor.TopLeft, Pivot.TopLeft), string.Empty, font: GUI.VideoTitleFont, textColor: new Color(253, 174, 0), textAlignment: Alignment.Left);
title = new GUITextBlock(new RectTransform(Point.Zero, textFrame.RectTransform, Anchor.TopLeft, Pivot.TopLeft), string.Empty, font: GUI.LargeFont, textColor: new Color(253, 174, 0), textAlignment: Alignment.Left);
textContent = new GUITextBlock(new RectTransform(Point.Zero, textFrame.RectTransform, Anchor.TopLeft, Pivot.TopLeft), string.Empty, font: GUI.Font, textAlignment: Alignment.TopLeft);
objectiveTitle = new GUITextBlock(new RectTransform(new Vector2(1f, 0f), textFrame.RectTransform, Anchor.TopCenter, Pivot.TopCenter), string.Empty, font: GUI.ObjectiveTitleFont, textAlignment: Alignment.CenterRight, textColor: Color.White);
objectiveTitle = new GUITextBlock(new RectTransform(new Vector2(1f, 0f), textFrame.RectTransform, Anchor.TopCenter, Pivot.TopCenter), string.Empty, font: GUI.SubHeadingFont, textAlignment: Alignment.CenterRight, textColor: Color.White);
objectiveTitle.Text = TextManager.Get("Tutorial.NewObjective");
objectiveText = new GUITextBlock(new RectTransform(Point.Zero, textFrame.RectTransform, Anchor.TopCenter, Pivot.TopCenter), string.Empty, font: GUI.ObjectiveNameFont, textColor: new Color(4, 180, 108), textAlignment: Alignment.CenterRight);
objectiveText = new GUITextBlock(new RectTransform(Point.Zero, textFrame.RectTransform, Anchor.TopCenter, Pivot.TopCenter), string.Empty, font: GUI.SubHeadingFont, textColor: new Color(4, 180, 108), textAlignment: Alignment.CenterRight);
objectiveTitle.Visible = objectiveText.Visible = false;
}
@@ -111,9 +111,7 @@ namespace Barotrauma
if (currentVideo == null) return;
if (currentVideo.IsPlaying) return;
currentVideo.Dispose();
currentVideo = null;
currentVideo = CreateVideo(scaledVideoResolution);
currentVideo.Play();
}
public void AddToGUIUpdateList(bool ignoreChildren = false, int order = 0)
@@ -140,7 +138,7 @@ namespace Barotrauma
currentVideo = null;
}
currentVideo = CreateVideo(scaledVideoResolution);
currentVideo = CreateVideo();
title.Text = textSettings != null ? TextManager.Get(contentId) : string.Empty;
textContent.Text = textSettings != null ? textSettings.Text : string.Empty;
objectiveText.Text = objective;
@@ -178,10 +176,10 @@ namespace Barotrauma
background.RectTransform.NonScaledSize = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
videoFrame.RectTransform.NonScaledSize += scaledVideoResolution + new Point(scaledBorderSize, scaledBorderSize);
videoView.RectTransform.NonScaledSize += scaledVideoResolution;
videoFrame.RectTransform.NonScaledSize = scaledVideoResolution + new Point(scaledBorderSize, scaledBorderSize);
videoView.RectTransform.NonScaledSize = scaledVideoResolution;
title.RectTransform.NonScaledSize += new Point(scaledTextWidth, scaledTitleHeight);
title.RectTransform.NonScaledSize = new Point(scaledTextWidth, scaledTitleHeight);
title.RectTransform.AbsoluteOffset = new Point((int)(5 * GUI.Scale), (int)(10 * GUI.Scale));
if (textSettings != null && !string.IsNullOrEmpty(textSettings.Text))
@@ -189,7 +187,7 @@ namespace Barotrauma
textSettings.Text = ToolBox.WrapText(textSettings.Text, scaledTextWidth, GUI.Font);
int wrappedHeight = textSettings.Text.Split('\n').Length * scaledTextHeight;
textFrame.RectTransform.NonScaledSize += new Point(scaledTextWidth + scaledBorderSize, wrappedHeight + scaledBorderSize + scaledButtonSize.Y + scaledTitleHeight);
textFrame.RectTransform.NonScaledSize = new Point(scaledTextWidth + scaledBorderSize, wrappedHeight + scaledBorderSize + scaledButtonSize.Y + scaledTitleHeight);
if (useTextOnRightSide)
{
@@ -200,7 +198,7 @@ namespace Barotrauma
textFrame.RectTransform.AbsoluteOffset = new Point(0, scaledVideoResolution.Y + scaledBorderSize * 2);
}
textContent.RectTransform.NonScaledSize += new Point(scaledTextWidth, wrappedHeight);
textContent.RectTransform.NonScaledSize = new Point(scaledTextWidth, wrappedHeight);
textContent.RectTransform.AbsoluteOffset = new Point(0, scaledBorderSize + scaledTitleHeight);
}
@@ -212,7 +210,7 @@ namespace Barotrauma
objectiveText.RectTransform.AbsoluteOffset = new Point(scaledXOffset, textContent.RectTransform.Rect.Height + objectiveTitle.Rect.Height + (int)(scaledTextHeight * 2.25f));
textFrame.RectTransform.NonScaledSize += new Point(0, scaledObjectiveFrameHeight);
objectiveText.RectTransform.NonScaledSize += new Point(textFrame.Rect.Width, scaledTextHeight);
objectiveText.RectTransform.NonScaledSize = new Point(textFrame.Rect.Width, scaledTextHeight);
objectiveTitle.Visible = objectiveText.Visible = true;
}
else
@@ -258,13 +256,13 @@ namespace Barotrauma
}
}
private Video CreateVideo(Point resolution)
private Video CreateVideo()
{
Video video = null;
try
{
video = new Video(GameMain.Instance.GraphicsDevice, GameMain.SoundManager, filePath, (uint)resolution.X, (uint)resolution.Y);
video = Video.Load(GameMain.Instance.GraphicsDevice, GameMain.SoundManager, filePath);
}
catch (Exception e)
{

View File

@@ -41,7 +41,7 @@ namespace Barotrauma
/// </summary>
public bool isFilled;
public int inputAreaMargin;
public Color color = Color.Red;
public Color color = GUI.Style.Red;
public Color? secondaryColor;
public Color textColor = Color.White;
public Color textBackgroundColor = Color.Black * 0.5f;
@@ -65,7 +65,7 @@ namespace Barotrauma
public object data;
public bool IsSelected => enabled && selectedWidgets.Contains(this);
public bool IsControlled => IsSelected && PlayerInput.LeftButtonHeld();
public bool IsControlled => IsSelected && PlayerInput.PrimaryMouseButtonHeld();
public bool IsMouseOver => GUI.MouseOn == null && InputRect.Contains(PlayerInput.MousePosition);
private bool enabled = true;
public bool Enabled
@@ -111,10 +111,10 @@ namespace Barotrauma
{
PreUpdate?.Invoke(deltaTime);
if (!enabled) { return; }
if (IsMouseOver || (!RequireMouseOn && selectedWidgets.Contains(this) && PlayerInput.LeftButtonHeld()))
if (IsMouseOver || (!RequireMouseOn && selectedWidgets.Contains(this) && PlayerInput.PrimaryMouseButtonHeld()))
{
Hovered?.Invoke();
if (RequireMouseOn || PlayerInput.LeftButtonDown())
if (RequireMouseOn || PlayerInput.PrimaryMouseButtonDown())
{
if ((multiselect && !selectedWidgets.Contains(this)) || selectedWidgets.None())
{
@@ -130,15 +130,15 @@ namespace Barotrauma
}
if (IsSelected)
{
if (PlayerInput.LeftButtonDown())
if (PlayerInput.PrimaryMouseButtonDown())
{
MouseDown?.Invoke();
}
if (PlayerInput.LeftButtonHeld())
if (PlayerInput.PrimaryMouseButtonHeld())
{
MouseHeld?.Invoke(deltaTime);
}
if (PlayerInput.LeftButtonClicked())
if (PlayerInput.PrimaryMouseButtonClicked())
{
MouseUp?.Invoke();
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,248 @@
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma
{
partial class MultiPlayerCampaign : CampaignMode
{
public bool SuppressStateSending = false;
private UInt16 startWatchmanID, endWatchmanID;
public static void StartCampaignSetup(IEnumerable<string> saveFiles)
{
var parent = GameMain.NetLobbyScreen.CampaignSetupFrame;
parent.ClearChildren();
parent.Visible = true;
GameMain.NetLobbyScreen.HighlightMode(2);
var layout = new GUILayoutGroup(new RectTransform(Vector2.One, parent.RectTransform, Anchor.Center))
{
Stretch = true
};
var buttonContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.07f), layout.RectTransform) { RelativeOffset = new Vector2(0.0f, 0.1f) }, isHorizontal: true)
{
RelativeSpacing = 0.02f
};
var campaignContainer = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.9f), layout.RectTransform, Anchor.BottomLeft), style: "InnerFrame")
{
CanBeFocused = false
};
var newCampaignContainer = new GUIFrame(new RectTransform(new Vector2(0.95f, 0.95f), campaignContainer.RectTransform, Anchor.Center), style: null);
var loadCampaignContainer = new GUIFrame(new RectTransform(new Vector2(0.95f, 0.95f), campaignContainer.RectTransform, Anchor.Center), style: null);
var campaignSetupUI = new CampaignSetupUI(true, newCampaignContainer, loadCampaignContainer, null, saveFiles);
var newCampaignButton = new GUIButton(new RectTransform(new Vector2(0.5f, 1.0f), buttonContainer.RectTransform),
TextManager.Get("NewCampaign"), style: "GUITabButton")
{
Selected = true
};
var loadCampaignButton = new GUIButton(new RectTransform(new Vector2(0.5f, 1.00f), buttonContainer.RectTransform),
TextManager.Get("LoadCampaign"), style: "GUITabButton");
newCampaignButton.OnClicked = (btn, obj) =>
{
newCampaignButton.Selected = true;
loadCampaignButton.Selected = false;
newCampaignContainer.Visible = true;
loadCampaignContainer.Visible = false;
return true;
};
loadCampaignButton.OnClicked = (btn, obj) =>
{
newCampaignButton.Selected = false;
loadCampaignButton.Selected = true;
newCampaignContainer.Visible = false;
loadCampaignContainer.Visible = true;
return true;
};
loadCampaignContainer.Visible = false;
GUITextBlock.AutoScaleAndNormalize(newCampaignButton.TextBlock, loadCampaignButton.TextBlock);
campaignSetupUI.StartNewGame = GameMain.Client.SetupNewCampaign;
campaignSetupUI.LoadGame = GameMain.Client.SetupLoadCampaign;
}
public override void Update(float deltaTime)
{
base.Update(deltaTime);
if (startWatchmanID > 0 && startWatchman == null)
{
startWatchman = Entity.FindEntityByID(startWatchmanID) as Character;
if (startWatchman != null) { InitializeWatchman(startWatchman); }
}
if (endWatchmanID > 0 && endWatchman == null)
{
endWatchman = Entity.FindEntityByID(endWatchmanID) as Character;
if (endWatchman != null) { InitializeWatchman(endWatchman); }
}
}
protected override void WatchmanInteract(Character watchman, Character interactor)
{
if ((watchman.Submarine == Level.Loaded.StartOutpost && !Submarine.MainSub.AtStartPosition) ||
(watchman.Submarine == Level.Loaded.EndOutpost && !Submarine.MainSub.AtEndPosition))
{
return;
}
if (GUIMessageBox.MessageBoxes.Any(mbox => mbox.UserData as string == "watchmanprompt"))
{
return;
}
if (GameMain.Client != null && interactor == Character.Controlled)
{
var msgBox = new GUIMessageBox("", TextManager.GetWithVariable("CampaignEnterOutpostPrompt", "[locationname]",
Submarine.MainSub.AtStartPosition ? Map.CurrentLocation.Name : Map.SelectedLocation.Name),
new string[] { TextManager.Get("Yes"), TextManager.Get("No") })
{
UserData = "watchmanprompt"
};
msgBox.Buttons[0].OnClicked = (btn, userdata) =>
{
GameMain.Client.RequestRoundEnd();
return true;
};
msgBox.Buttons[0].OnClicked += msgBox.Close;
msgBox.Buttons[1].OnClicked += msgBox.Close;
}
}
public void ClientWrite(IWriteMessage msg)
{
System.Diagnostics.Debug.Assert(map.Locations.Count < UInt16.MaxValue);
msg.Write(map.SelectedLocationIndex == -1 ? UInt16.MaxValue : (UInt16)map.SelectedLocationIndex);
msg.Write(map.SelectedMissionIndex == -1 ? byte.MaxValue : (byte)map.SelectedMissionIndex);
msg.Write(PurchasedHullRepairs);
msg.Write(PurchasedItemRepairs);
msg.Write(PurchasedLostShuttles);
msg.Write((UInt16)CargoManager.PurchasedItems.Count);
foreach (PurchasedItem pi in CargoManager.PurchasedItems)
{
msg.Write(pi.ItemPrefab.Identifier);
msg.Write((UInt16)pi.Quantity);
}
}
//static because we may need to instantiate the campaign if it hasn't been done yet
public static void ClientRead(IReadMessage msg)
{
byte campaignID = msg.ReadByte();
UInt16 updateID = msg.ReadUInt16();
UInt16 saveID = msg.ReadUInt16();
string mapSeed = msg.ReadString();
UInt16 currentLocIndex = msg.ReadUInt16();
UInt16 selectedLocIndex = msg.ReadUInt16();
byte selectedMissionIndex = msg.ReadByte();
UInt16 startWatchmanID = msg.ReadUInt16();
UInt16 endWatchmanID = msg.ReadUInt16();
int money = msg.ReadInt32();
bool purchasedHullRepairs = msg.ReadBoolean();
bool purchasedItemRepairs = msg.ReadBoolean();
bool purchasedLostShuttles = msg.ReadBoolean();
UInt16 purchasedItemCount = msg.ReadUInt16();
List<PurchasedItem> purchasedItems = new List<PurchasedItem>();
for (int i = 0; i < purchasedItemCount; i++)
{
string itemPrefabIdentifier = msg.ReadString();
UInt16 itemQuantity = msg.ReadUInt16();
purchasedItems.Add(new PurchasedItem(ItemPrefab.Prefabs[itemPrefabIdentifier], itemQuantity));
}
bool hasCharacterData = msg.ReadBoolean();
CharacterInfo myCharacterInfo = null;
if (hasCharacterData)
{
myCharacterInfo = CharacterInfo.ClientRead(CharacterPrefab.HumanSpeciesName, msg);
}
MultiPlayerCampaign campaign = GameMain.GameSession?.GameMode as MultiPlayerCampaign;
if (campaign == null || campaignID != campaign.CampaignID)
{
string savePath = SaveUtil.CreateSavePath(SaveUtil.SaveType.Multiplayer);
GameMain.GameSession = new GameSession(null, savePath,
GameModePreset.List.Find(g => g.Identifier == "multiplayercampaign"));
campaign = ((MultiPlayerCampaign)GameMain.GameSession.GameMode);
campaign.CampaignID = campaignID;
campaign.GenerateMap(mapSeed);
GameMain.NetLobbyScreen.ToggleCampaignMode(true);
}
//server has a newer save file
if (NetIdUtils.IdMoreRecent(saveID, campaign.PendingSaveID))
{
/*//stop any active campaign save transfers, they're outdated now
List<FileReceiver.FileTransferIn> saveTransfers =
GameMain.Client.FileReceiver.ActiveTransfers.FindAll(t => t.FileType == FileTransferType.CampaignSave);
foreach (var transfer in saveTransfers)
{
GameMain.Client.FileReceiver.StopTransfer(transfer);
}
GameMain.Client.RequestFile(FileTransferType.CampaignSave, null, null);*/
campaign.PendingSaveID = saveID;
}
if (NetIdUtils.IdMoreRecent(updateID, campaign.lastUpdateID))
{
campaign.SuppressStateSending = true;
//we need to have the latest save file to display location/mission/store
if (campaign.LastSaveID == saveID)
{
campaign.Map.SetLocation(currentLocIndex == UInt16.MaxValue ? -1 : currentLocIndex);
campaign.Map.SelectLocation(selectedLocIndex == UInt16.MaxValue ? -1 : selectedLocIndex);
campaign.Map.SelectMission(selectedMissionIndex);
campaign.CargoManager.SetPurchasedItems(purchasedItems);
}
campaign.startWatchmanID = startWatchmanID;
campaign.endWatchmanID = endWatchmanID;
campaign.Money = money;
campaign.PurchasedHullRepairs = purchasedHullRepairs;
campaign.PurchasedItemRepairs = purchasedItemRepairs;
campaign.PurchasedLostShuttles = purchasedLostShuttles;
if (myCharacterInfo != null)
{
GameMain.Client.CharacterInfo = myCharacterInfo;
GameMain.NetLobbyScreen.SetCampaignCharacterInfo(myCharacterInfo);
}
else
{
GameMain.NetLobbyScreen.SetCampaignCharacterInfo(null);
}
campaign.lastUpdateID = updateID;
campaign.SuppressStateSending = false;
}
}
public override void Save(XElement element)
{
//do nothing, the clients get the save files from the server
}
}
}

View File

@@ -32,11 +32,12 @@ namespace Barotrauma
OnClicked = (btn, userdata) => { TryEndRound(GetLeavingSub()); return true; }
};
foreach (JobPrefab jobPrefab in JobPrefab.List.Values)
foreach (JobPrefab jobPrefab in JobPrefab.Prefabs)
{
for (int i = 0; i < jobPrefab.InitialCount; i++)
{
CrewManager.AddCharacterInfo(new CharacterInfo(Character.HumanSpeciesName, "", jobPrefab));
var variant = Rand.Range(0, jobPrefab.Variants);
CrewManager.AddCharacterInfo(new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: jobPrefab, variant: variant));
}
}
}
@@ -90,7 +91,7 @@ namespace Barotrauma
if (Character.Controlled.Submarine != outpost) { return null; }
//if there's a sub docked to the outpost, we can leave the level
if (outpost.DockedTo.Count > 0)
if (outpost.DockedTo.Any())
{
var dockedSub = outpost.DockedTo.FirstOrDefault();
return dockedSub.DockedTo.Contains(Submarine.MainSub) ? Submarine.MainSub : dockedSub;
@@ -292,7 +293,7 @@ namespace Barotrauma
summaryScreen.RemoveChild(summaryScreen.Children.FirstOrDefault(c => c is GUIButton));
var okButton = new GUIButton(new RectTransform(new Vector2(0.2f, 1.0f), buttonArea.RectTransform),
var okButton = new GUIButton(new RectTransform(new Vector2(0.25f, 1.0f), buttonArea.RectTransform),
TextManager.Get("LoadGameButton"))
{
OnClicked = (GUIButton button, object obj) =>
@@ -304,7 +305,7 @@ namespace Barotrauma
}
};
var quitButton = new GUIButton(new RectTransform(new Vector2(0.2f, 1.0f), buttonArea.RectTransform),
var quitButton = new GUIButton(new RectTransform(new Vector2(0.25f, 1.0f), buttonArea.RectTransform),
TextManager.Get("QuitButton"));
quitButton.OnClicked += GameMain.LobbyScreen.QuitToMainMenu;
quitButton.OnClicked += (GUIButton button, object obj) =>
@@ -401,14 +402,17 @@ namespace Barotrauma
campaign.Money = element.GetAttributeInt("money", 0);
campaign.CheatsEnabled = element.GetAttributeBool("cheatsenabled", false);
campaign.InitialSuppliesSpawned = element.GetAttributeBool("initialsuppliesspawned", false);
if (campaign.CheatsEnabled)
{
DebugConsole.CheatsEnabled = true;
if (Steam.SteamManager.USE_STEAM && !SteamAchievementManager.CheatsEnabled)
#if USE_STEAM
if (!SteamAchievementManager.CheatsEnabled)
{
SteamAchievementManager.CheatsEnabled = true;
new GUIMessageBox("Cheats enabled", "Cheat commands have been enabled on the campaign. You will not receive Steam Achievements until you restart the game.");
}
#endif
}
//backwards compatibility with older save files
@@ -429,7 +433,8 @@ namespace Barotrauma
XElement modeElement = new XElement("SinglePlayerCampaign",
// Refunds the money when save & quitting from the map if there are items selected in the store
new XAttribute("money", Money + (CargoManager != null ? CargoManager.GetTotalItemCost() : 0)),
new XAttribute("cheatsenabled", CheatsEnabled));
new XAttribute("cheatsenabled", CheatsEnabled),
new XAttribute("initialsuppliesspawned", InitialSuppliesSpawned));
CrewManager.Save(modeElement);
Map.Save(modeElement);
element.Add(modeElement);

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using Barotrauma.Items.Components;
using Barotrauma.Networking;
@@ -56,17 +57,30 @@ namespace Barotrauma.Tutorials
radioSpeakerName = TextManager.Get("Tutorial.Radio.Watchman");
GameMain.GameSession.CrewManager.AllowCharacterSwitch = false;
var revolver = captain.Inventory.FindItemByIdentifier("revolver");
var revolver = FindOrGiveItem(captain, "revolver");
revolver.Unequip(captain);
captain.Inventory.RemoveItem(revolver);
var captainscap = captain.Inventory.FindItemByIdentifier("captainscap");
var captainscap =
captain.Inventory.FindItemByIdentifier("captainscap1") ??
captain.Inventory.FindItemByIdentifier("captainscap2") ??
captain.Inventory.FindItemByIdentifier("captainscap3");
if (captainscap != null)
{
captainscap.Unequip(captain);
captain.Inventory.RemoveItem(captainscap);
}
var captainsuniform = captain.Inventory.FindItemByIdentifier("captainsuniform");
var captainsuniform =
captain.Inventory.FindItemByIdentifier("captainsuniform1") ??
captain.Inventory.FindItemByIdentifier("captainsuniform2") ??
captain.Inventory.FindItemByIdentifier("captainsuniform3");
if (captainsuniform != null)
{
captainsuniform.Unequip(captain);
captain.Inventory.RemoveItem(captainsuniform);
}
var steerOrder = Order.GetPrefab("steer");
captain_steerIcon = steerOrder.SymbolSprite;
@@ -85,7 +99,7 @@ namespace Barotrauma.Tutorials
captain_medicSpawnPos = Item.ItemList.Find(i => i.HasTag("captain_medicspawnpos")).WorldPosition;
tutorial_submarineDoor = Item.ItemList.Find(i => i.HasTag("tutorial_submarinedoor")).GetComponent<Door>();
tutorial_submarineDoorLight = Item.ItemList.Find(i => i.HasTag("tutorial_submarinedoorlight")).GetComponent<LightComponent>();
var medicInfo = new CharacterInfo(Character.HumanSpeciesName, "", JobPrefab.Get("medicaldoctor"));
var medicInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, "", JobPrefab.Get("medicaldoctor"));
captain_medic = Character.Create(medicInfo, captain_medicSpawnPos, "medicaldoctor");
captain_medic.GiveJobItems(null);
captain_medic.CanSpeak = captain_medic.AIController.Enabled = false;
@@ -107,15 +121,15 @@ namespace Barotrauma.Tutorials
SetDoorAccess(tutorial_lockedDoor_1, null, false);
SetDoorAccess(tutorial_lockedDoor_2, null, false);
var mechanicInfo = new CharacterInfo(Character.HumanSpeciesName, "", JobPrefab.Get("mechanic"));
var mechanicInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, "", JobPrefab.Get("mechanic"));
captain_mechanic = Character.Create(mechanicInfo, WayPoint.GetRandom(SpawnType.Human, mechanicInfo.Job, Submarine.MainSub).WorldPosition, "mechanic");
captain_mechanic.GiveJobItems();
var securityInfo = new CharacterInfo(Character.HumanSpeciesName, "", JobPrefab.Get("securityofficer"));
var securityInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, "", JobPrefab.Get("securityofficer"));
captain_security = Character.Create(securityInfo, WayPoint.GetRandom(SpawnType.Human, securityInfo.Job, Submarine.MainSub).WorldPosition, "securityofficer");
captain_security.GiveJobItems();
var engineerInfo = new CharacterInfo(Character.HumanSpeciesName, "", JobPrefab.Get("engineer"));
var engineerInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, "", JobPrefab.Get("engineer"));
captain_engineer = Character.Create(engineerInfo, WayPoint.GetRandom(SpawnType.Human, engineerInfo.Job, Submarine.MainSub).WorldPosition, "engineer");
captain_engineer.GiveJobItems();
@@ -143,13 +157,14 @@ namespace Barotrauma.Tutorials
do { yield return null; } while (!captain_medicObjectiveSensor.MotionDetected);
GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(captain_medic.Info.DisplayName, TextManager.Get("Captain.Radio.Medic"), ChatMessageType.Radio, null);
yield return new WaitForSeconds(2f, false);
GameMain.GameSession.CrewManager.ToggleCrewAreaOpen = true;
GameMain.GameSession.CrewManager.ToggleCrewListOpen = true;
GameMain.GameSession.CrewManager.AddCharacter(captain_medic);
TriggerTutorialSegment(0);
TriggerTutorialSegment(0, GameMain.Config.KeyBindText(InputType.Command));
do
{
yield return null;
GameMain.GameSession.CrewManager.HighlightOrderButton(captain_medic, "follow", highlightColor, new Vector2(5, 5));
// TODO: Rework order highlighting for new command UI
// GameMain.GameSession.CrewManager.HighlightOrderButton(captain_medic, "follow", highlightColor, new Vector2(5, 5));
}
while (!HasOrder(captain_medic, "follow"));
SetDoorAccess(tutorial_submarineDoor, tutorial_submarineDoorLight, true);
@@ -159,34 +174,37 @@ namespace Barotrauma.Tutorials
do { yield return null; } while (!captain_enteredSubmarineSensor.MotionDetected);
yield return new WaitForSeconds(3f, false);
captain_mechanic.AIController.Enabled = captain_security.AIController.Enabled = captain_engineer.AIController.Enabled = true;
TriggerTutorialSegment(1);
TriggerTutorialSegment(1, GameMain.Config.KeyBindText(InputType.Command));
GameMain.GameSession.CrewManager.AddCharacter(captain_mechanic);
do
{
yield return null;
GameMain.GameSession.CrewManager.HighlightOrderButton(captain_mechanic, "repairsystems", highlightColor, new Vector2(5, 5));
// TODO: Rework order highlighting for new command UI
// GameMain.GameSession.CrewManager.HighlightOrderButton(captain_mechanic, "repairsystems", highlightColor, new Vector2(5, 5));
//HighlightOrderOption("jobspecific");
}
while (!HasOrder(captain_mechanic, "repairsystems"));
RemoveCompletedObjective(segments[1]);
yield return new WaitForSeconds(2f, false);
TriggerTutorialSegment(2);
TriggerTutorialSegment(2, GameMain.Config.KeyBindText(InputType.Command));
GameMain.GameSession.CrewManager.AddCharacter(captain_security);
do
{
yield return null;
GameMain.GameSession.CrewManager.HighlightOrderButton(captain_security, "operateweapons", highlightColor, new Vector2(5, 5));
// TODO: Rework order highlighting for new command UI
// GameMain.GameSession.CrewManager.HighlightOrderButton(captain_security, "operateweapons", highlightColor, new Vector2(5, 5));
HighlightOrderOption("fireatwill");
}
while (!HasOrder(captain_security, "operateweapons", "fireatwill"));
RemoveCompletedObjective(segments[2]);
yield return new WaitForSeconds(4f, false);
TriggerTutorialSegment(3);
TriggerTutorialSegment(3, GameMain.Config.KeyBindText(InputType.Command));
GameMain.GameSession.CrewManager.AddCharacter(captain_engineer);
do
{
yield return null;
GameMain.GameSession.CrewManager.HighlightOrderButton(captain_engineer, "operatereactor", highlightColor, new Vector2(5, 5));
// TODO: Rework order highlighting for new command UI
// GameMain.GameSession.CrewManager.HighlightOrderButton(captain_engineer, "operatereactor", highlightColor, new Vector2(5, 5));
HighlightOrderOption("powerup");
}
while (!HasOrder(captain_engineer, "operatereactor", "powerup"));
@@ -204,7 +222,7 @@ namespace Barotrauma.Tutorials
{
//captain_navConsoleCustomInterface.HighlightElement(0, uiHighlightColor, duration: 1.0f, pulsateAmount: 0.0f);
yield return new WaitForSeconds(1.0f, false);
} while (Submarine.MainSub.DockedTo.Count > 0);
} while (Submarine.MainSub.DockedTo.Any());
RemoveCompletedObjective(segments[4]);
yield return new WaitForSeconds(2f, false);
TriggerTutorialSegment(5); // Navigate to destination
@@ -212,10 +230,9 @@ namespace Barotrauma.Tutorials
{
if (IsSelectedItem(captain_navConsole.Item))
{
if (captain_sonar.ActiveTickBox.Box.FlashTimer <= 0)
if (captain_sonar.SonarModeSwitch.Frame.FlashTimer <= 0)
{
captain_sonar.ActiveTickBox.Box.Flash(highlightColor, 1.5f, false, new Vector2(2.5f, 2.5f));
//captain_sonar.ActiveTickBox.Box.Pulsate(Vector2.One, Vector2.One * 1.5f, 1.5f);
captain_sonar.SonarModeSwitch.Frame.Flash(highlightColor, 1.5f, false, false, new Vector2(2.5f, 2.5f));
}
}
yield return null;
@@ -229,7 +246,7 @@ namespace Barotrauma.Tutorials
{
//captain_navConsoleCustomInterface.HighlightElement(0, uiHighlightColor, duration: 1.0f, pulsateAmount: 0.0f);
yield return new WaitForSeconds(1.0f, false);
} while (!Submarine.MainSub.AtEndPosition || Submarine.MainSub.DockedTo.Count == 0);
} while (!Submarine.MainSub.AtEndPosition || Submarine.MainSub.DockedTo.Any());
RemoveCompletedObjective(segments[6]);
yield return new WaitForSeconds(3f, false);
GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.GetWithVariable("Captain.Radio.Complete", "[OUTPOSTNAME]", GameMain.GameSession.EndLocation.Name), ChatMessageType.Radio, null);

View File

@@ -22,7 +22,6 @@ namespace Barotrauma.Tutorials
private ItemContainer doctor_medBayCabinet;
private Character patient1, patient2;
private List<Character> subPatients;
private Hull startRoom;
private Hull medBay;
private Door doctor_firstDoor;
@@ -56,6 +55,22 @@ namespace Barotrauma.Tutorials
radioSpeakerName = TextManager.Get("Tutorial.Radio.Speaker");
doctor = Character.Controlled;
var bandages = FindOrGiveItem(doctor, "antibleeding1");
bandages.Unequip(doctor);
doctor.Inventory.RemoveItem(bandages);
var syringegun = FindOrGiveItem(doctor, "syringegun");
syringegun.Unequip(doctor);
doctor.Inventory.RemoveItem(syringegun);
var antibiotics = FindOrGiveItem(doctor, "antibiotics");
antibiotics.Unequip(doctor);
doctor.Inventory.RemoveItem(antibiotics);
var morphine = FindOrGiveItem(doctor, "antidama1");
morphine.Unequip(doctor);
doctor.Inventory.RemoveItem(morphine);
doctor_suppliesCabinet = Item.ItemList.Find(i => i.HasTag("doctor_suppliescabinet"))?.GetComponent<ItemContainer>();
doctor_medBayCabinet = Item.ItemList.Find(i => i.HasTag("doctor_medbaycabinet"))?.GetComponent<ItemContainer>();
@@ -63,30 +78,30 @@ namespace Barotrauma.Tutorials
var patientHull2 = WayPoint.WayPointList.Find(wp => wp.IdCardDesc == "airlock").CurrentHull;
medBay = WayPoint.WayPointList.Find(wp => wp.IdCardDesc == "medbay").CurrentHull;
var assistantInfo = new CharacterInfo(Character.HumanSpeciesName, "", JobPrefab.Get("assistant"));
var assistantInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, "", JobPrefab.Get("assistant"));
patient1 = Character.Create(assistantInfo, patientHull1.WorldPosition, "1");
patient1.GiveJobItems(null);
patient1.CanSpeak = false;
patient1.AddDamage(patient1.WorldPosition, new List<Affliction>() { new Affliction(AfflictionPrefab.Burn, 45.0f) }, stun: 0, playSound: false);
patient1.AIController.Enabled = false;
assistantInfo = new CharacterInfo(Character.HumanSpeciesName, "", JobPrefab.Get("assistant"));
assistantInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, "", JobPrefab.Get("assistant"));
patient2 = Character.Create(assistantInfo, patientHull2.WorldPosition, "2");
patient2.GiveJobItems(null);
patient2.CanSpeak = false;
patient2.AIController.Enabled = false;
var mechanicInfo = new CharacterInfo(Character.HumanSpeciesName, "", JobPrefab.Get("engineer"));
var mechanicInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, "", JobPrefab.Get("engineer"));
var subPatient1 = Character.Create(mechanicInfo, WayPoint.GetRandom(SpawnType.Human, mechanicInfo.Job, Submarine.MainSub).WorldPosition, "3");
subPatient1.AddDamage(patient1.WorldPosition, new List<Affliction>() { new Affliction(AfflictionPrefab.Burn, 40.0f) }, stun: 0, playSound: false);
subPatients.Add(subPatient1);
var securityInfo = new CharacterInfo(Character.HumanSpeciesName, "", JobPrefab.Get("securityofficer"));
var securityInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, "", JobPrefab.Get("securityofficer"));
var subPatient2 = Character.Create(securityInfo, WayPoint.GetRandom(SpawnType.Human, securityInfo.Job, Submarine.MainSub).WorldPosition, "3");
subPatient2.AddDamage(patient1.WorldPosition, new List<Affliction>() { new Affliction(AfflictionPrefab.InternalDamage, 40.0f) }, stun: 0, playSound: false);
subPatients.Add(subPatient2);
var engineerInfo = new CharacterInfo(Character.HumanSpeciesName, "", JobPrefab.Get("engineer"));
var engineerInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, "", JobPrefab.Get("engineer"));
var subPatient3 = Character.Create(securityInfo, WayPoint.GetRandom(SpawnType.Human, engineerInfo.Job, Submarine.MainSub).WorldPosition, "3");
subPatient3.AddDamage(patient1.WorldPosition, new List<Affliction>() { new Affliction(AfflictionPrefab.Burn, 20.0f) }, stun: 0, playSound: false);
subPatients.Add(subPatient3);
@@ -176,7 +191,7 @@ namespace Barotrauma.Tutorials
yield return new WaitForSeconds(2.0f);
}*/
TriggerTutorialSegment(0, GameMain.Config.KeyBind(InputType.Select), GameMain.Config.KeyBind(InputType.Deselect)); // Medical supplies objective
TriggerTutorialSegment(0, GameMain.Config.KeyBindText(InputType.Select), GameMain.Config.KeyBindText(InputType.Deselect), GameMain.Config.KeyBindText(InputType.ToggleInventory)); // Medical supplies objective
do
{
@@ -205,7 +220,7 @@ namespace Barotrauma.Tutorials
// 2nd tutorial segment, treat self -------------------------------------------------------------------------
TriggerTutorialSegment(1, GameMain.Config.KeyBind(InputType.Health)); // Open health interface
TriggerTutorialSegment(1, GameMain.Config.KeyBindText(InputType.Health)); // Open health interface
while (CharacterHealth.OpenHealthWindow == null)
{
doctor.CharacterHealth.HealthBarPulsateTimer = 1.0f;
@@ -255,17 +270,18 @@ namespace Barotrauma.Tutorials
GameMain.GameSession.CrewManager.AllowCharacterSwitch = false;
GameMain.GameSession.CrewManager.AddCharacter(doctor);
GameMain.GameSession.CrewManager.AddCharacter(patient1);
GameMain.GameSession.CrewManager.ToggleCrewAreaOpen = true;
GameMain.GameSession.CrewManager.ToggleCrewListOpen = true;
patient1.CharacterHealth.UseHealthWindow = false;
yield return new WaitForSeconds(3.0f, false);
patient1.AIController.Enabled = true;
doctor.RemoveActiveObjectiveEntity(patient1);
TriggerTutorialSegment(3); // Get the patient to medbay
TriggerTutorialSegment(3, GameMain.Config.KeyBindText(InputType.Command)); // Get the patient to medbay
while (patient1.CurrentOrder == null || patient1.CurrentOrder.Identifier != "follow")
{
GameMain.GameSession.CrewManager.HighlightOrderButton(patient1, "follow", highlightColor, new Vector2(5, 5));
// TODO: Rework order highlighting for new command UI
// GameMain.GameSession.CrewManager.HighlightOrderButton(patient1, "follow", highlightColor, new Vector2(5, 5));
yield return null;
}
@@ -282,7 +298,7 @@ namespace Barotrauma.Tutorials
yield return new WaitForSeconds(2.0f, false);
TriggerTutorialSegment(4, GameMain.Config.KeyBind(InputType.Health)); // treat burns
TriggerTutorialSegment(4, GameMain.Config.KeyBindText(InputType.Health)); // treat burns
do
{
@@ -344,14 +360,18 @@ namespace Barotrauma.Tutorials
do { yield return null; } while (!tutorial_upperFinalDoor.IsOpen);
yield return new WaitForSeconds(2.0f, false);
TriggerTutorialSegment(5, GameMain.Config.KeyBind(InputType.Health)); // perform CPR
TriggerTutorialSegment(5, GameMain.Config.KeyBindText(InputType.Health)); // perform CPR
SetHighlight(patient2, true);
while (patient2.IsUnconscious)
{
if (CharacterHealth.OpenHealthWindow != null && doctor.AnimController.Anim != AnimController.Animation.CPR)
{
CharacterHealth.OpenHealthWindow.CPRButton.Pulsate(Vector2.One, Vector2.One * 1.5f, 1.0f);
CharacterHealth.OpenHealthWindow.CPRButton.Flash();
//Disabled pulse until it's replaced by a better effect
//CharacterHealth.OpenHealthWindow.CPRButton.Pulsate(Vector2.One, Vector2.One * 1.5f, 1.0f);
if (CharacterHealth.OpenHealthWindow.CPRButton.FlashTimer <= 0.0f)
{
CharacterHealth.OpenHealthWindow.CPRButton.Flash(highlightColor);
}
}
yield return null;
}
@@ -374,7 +394,7 @@ namespace Barotrauma.Tutorials
GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Doctor.Radio.EnteredSub"), ChatMessageType.Radio, null);
yield return new WaitForSeconds(3.0f, false);
TriggerTutorialSegment(6, GameMain.Config.KeyBind(InputType.Health)); // give treatment to anyone in need
TriggerTutorialSegment(6, GameMain.Config.KeyBindText(InputType.Health)); // give treatment to anyone in need
foreach (var patient in subPatients)
{

View File

@@ -87,7 +87,7 @@ namespace Barotrauma.Tutorials
radioSpeakerName = TextManager.Get("Tutorial.Radio.Speaker");
engineer = Character.Controlled;
var toolbox = engineer.Inventory.FindItemByIdentifier("toolbox");
var toolbox = FindOrGiveItem(engineer, "toolbox");
toolbox.Unequip(engineer);
engineer.Inventory.RemoveItem(toolbox);
@@ -127,7 +127,7 @@ namespace Barotrauma.Tutorials
engineer_reactor = Item.ItemList.Find(i => i.HasTag("engineer_reactor")).GetComponent<Reactor>();
engineer_reactor.FireDelay = engineer_reactor.MeltdownDelay = float.PositiveInfinity;
engineer_reactor.FuelConsumptionRate = 0.0f;
engineer_reactor.OnOffSwitch.BarScroll = 1f;
engineer_reactor.PowerOn = true;
reactorOperatedProperly = false;
engineer_secondDoor = Item.ItemList.Find(i => i.HasTag("engineer_seconddoor")).GetComponent<Door>(); ;
@@ -195,7 +195,7 @@ namespace Barotrauma.Tutorials
engineer_submarineJunctionBox_2 = Item.ItemList.Find(i => i.HasTag("engineer_submarinejunctionbox_2"));
engineer_submarineJunctionBox_3 = Item.ItemList.Find(i => i.HasTag("engineer_submarinejunctionbox_3"));
engineer_submarineReactor = Item.ItemList.Find(i => i.HasTag("engineer_submarinereactor")).GetComponent<Reactor>();
engineer_submarineReactor.OnOffSwitch.BarScrollValue = .25f;
engineer_submarineReactor.PowerOn = true;
engineer_submarineReactor.IsActive = engineer_submarineReactor.AutoTemp = false;
engineer_submarineJunctionBox_1.Indestructible = false;
@@ -235,7 +235,7 @@ namespace Barotrauma.Tutorials
do { yield return null; } while (!engineer_equipmentObjectiveSensor.MotionDetected);
GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Engineer.Radio.Equipment"), ChatMessageType.Radio, null);
yield return new WaitForSeconds(0.5f, false);
TriggerTutorialSegment(0, GameMain.Config.KeyBind(InputType.Select), GameMain.Config.KeyBind(InputType.Deselect)); // Retrieve equipment
TriggerTutorialSegment(0, GameMain.Config.KeyBindText(InputType.Select), GameMain.Config.KeyBindText(InputType.Deselect), GameMain.Config.KeyBindText(InputType.ToggleInventory)); // Retrieve equipment
bool firstSlotRemoved = false;
bool secondSlotRemoved = false;
bool thirdSlotRemoved = false;
@@ -289,19 +289,19 @@ namespace Barotrauma.Tutorials
{
if (IsSelectedItem(engineer_reactor.Item))
{
engineer_reactor.AutoTempSlider.BarScrollValue = 1.0f;
if (engineer_reactor.OnOffSwitch.FlashTimer <= 0)
engineer_reactor.AutoTemp = false;
if (engineer_reactor.PowerButton.FlashTimer <= 0)
{
engineer_reactor.OnOffSwitch.Flash(highlightColor, 1.5f, false);
engineer_reactor.PowerButton.Flash(highlightColor, 1.5f, false);
}
}
yield return null;
} while (engineer_reactor.OnOffSwitch.BarScroll > 0.45f);
} while (!engineer_reactor.PowerOn);
do
{
if (IsSelectedItem(engineer_reactor.Item) && engineer_reactor.Item.OwnInventory.slots != null)
{
engineer_reactor.AutoTempSlider.BarScrollValue = 1.0f;
engineer_reactor.AutoTemp = false;
HighlightInventorySlot(engineer.Inventory, "fuelrod", highlightColor, 0.5f, 0.5f, 0f);
for (int i = 0; i < engineer_reactor.Item.OwnInventory.slots.Length; i++)
@@ -316,7 +316,7 @@ namespace Barotrauma.Tutorials
{
if (IsSelectedItem(engineer_reactor.Item))
{
engineer_reactor.AutoTempSlider.BarScrollValue = 1.0f;
engineer_reactor.AutoTemp = false;
if (engineer_reactor.FissionRateScrollBar.FlashTimer <= 0)
{
engineer_reactor.FissionRateScrollBar.Flash(highlightColor, 1.5f);
@@ -335,9 +335,9 @@ namespace Barotrauma.Tutorials
{
if (IsSelectedItem(engineer_reactor.Item))
{
if (engineer_reactor.AutoTempSlider.FlashTimer <= 0)
if (engineer_reactor.AutoTempSwitch.FlashTimer <= 0)
{
engineer_reactor.AutoTempSlider.Flash(highlightColor, 1.5f, false, new Vector2(10, 10));
engineer_reactor.AutoTempSwitch.Flash(highlightColor, 1.5f, false, false, new Vector2(10, 10));
}
}
yield return null;
@@ -348,7 +348,7 @@ namespace Barotrauma.Tutorials
{
yield return new WaitForSeconds(0.1f, false);
wait -= 0.1f;
engineer_reactor.AutoTempSlider.BarScrollValue = 0.0f;
engineer_reactor.AutoTemp = true;
} while (wait > 0.0f);
engineer.SelectedConstruction = null;
engineer_reactor.CanBeSelected = false;
@@ -361,7 +361,7 @@ namespace Barotrauma.Tutorials
do { yield return null; } while (!engineer_secondDoor.IsOpen);
yield return new WaitForSeconds(1f, false);
Repairable repairableJunctionBoxComponent = engineer_brokenJunctionBox.GetComponent<Repairable>();
TriggerTutorialSegment(2, GameMain.Config.KeyBind(InputType.Select)); // Repair the junction box
TriggerTutorialSegment(2, GameMain.Config.KeyBindText(InputType.Select)); // Repair the junction box
do
{
if (!engineer.HasEquippedItem("screwdriver"))
@@ -389,7 +389,7 @@ namespace Barotrauma.Tutorials
do { yield return null; } while (!engineer_thirdDoor.IsOpen);
GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Engineer.Radio.FaultyWiring"), ChatMessageType.Radio, null);
yield return new WaitForSeconds(2f, false);
TriggerTutorialSegment(3, GameMain.Config.KeyBind(InputType.Use), GameMain.Config.KeyBind(InputType.Deselect)); // Connect the junction boxes
TriggerTutorialSegment(3, GameMain.Config.KeyBindText(InputType.Use), GameMain.Config.KeyBindText(InputType.Deselect)); // Connect the junction boxes
do { CheckGhostWires(); HandleJunctionBoxWiringHighlights(); yield return null; } while (engineer_workingPump.Voltage < engineer_workingPump.MinVoltage); // Wait until connected all the way to the pump
CheckGhostWires();
for (int i = 0; i < engineer_disconnectedJunctionBoxes.Length; i++)

View File

@@ -87,18 +87,18 @@ namespace Barotrauma.Tutorials
radioSpeakerName = TextManager.Get("Tutorial.Radio.Speaker");
mechanic = Character.Controlled;
var toolbox = mechanic.Inventory.FindItemByIdentifier("toolbox");
var toolbox = FindOrGiveItem(mechanic, "toolbox");
toolbox.Unequip(mechanic);
mechanic.Inventory.RemoveItem(toolbox);
var crowbar = mechanic.Inventory.FindItemByIdentifier("crowbar");
var crowbar = FindOrGiveItem(mechanic, "crowbar");
crowbar.Unequip(mechanic);
mechanic.Inventory.RemoveItem(crowbar);
var repairOrder = Order.GetPrefab("repairsystems");
mechanic_repairIcon = repairOrder.SymbolSprite;
mechanic_repairIconColor = repairOrder.Color;
mechanic_weldIcon = new Sprite("Content/UI/IconAtlas.png", new Rectangle(1, 256, 127, 127), new Vector2(0.5f, 0.5f));
mechanic_weldIcon = new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(1, 256, 127, 127), new Vector2(0.5f, 0.5f));
// Other tutorial items
tutorial_securityFinalDoorLight = Item.ItemList.Find(i => i.HasTag("tutorial_securityfinaldoorlight")).GetComponent<LightComponent>();
@@ -241,7 +241,7 @@ namespace Barotrauma.Tutorials
GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Mechanic.Radio.WakeUp"), ChatMessageType.Radio, null);
yield return new WaitForSeconds(2.5f, false);
TriggerTutorialSegment(0, GameMain.Config.KeyBind(InputType.Up), GameMain.Config.KeyBind(InputType.Left), GameMain.Config.KeyBind(InputType.Down), GameMain.Config.KeyBind(InputType.Right), GameMain.Config.KeyBind(InputType.Select), GameMain.Config.KeyBind(InputType.Select)); // Open door objective
TriggerTutorialSegment(0, GameMain.Config.KeyBindText(InputType.Up), GameMain.Config.KeyBindText(InputType.Left), GameMain.Config.KeyBindText(InputType.Down), GameMain.Config.KeyBindText(InputType.Right), GameMain.Config.KeyBindText(InputType.Select), GameMain.Config.KeyBindText(InputType.Select)); // Open door objective
yield return new WaitForSeconds(0.0f, false);
SetDoorAccess(mechanic_firstDoor, mechanic_firstDoorLight, true);
SetHighlight(mechanic_firstDoor.Item, true);
@@ -254,7 +254,7 @@ namespace Barotrauma.Tutorials
yield return new WaitForSeconds(0.0f, false);
GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Mechanic.Radio.Equipment"), ChatMessageType.Radio, null);
do { yield return null; } while (!mechanic_equipmentObjectiveSensor.MotionDetected);
TriggerTutorialSegment(1, GameMain.Config.KeyBind(InputType.Select), GameMain.Config.KeyBind(InputType.Deselect)); // Equipment & inventory objective
TriggerTutorialSegment(1, GameMain.Config.KeyBindText(InputType.Select), GameMain.Config.KeyBindText(InputType.Deselect), GameMain.Config.KeyBindText(InputType.ToggleInventory)); // Equipment & inventory objective
SetHighlight(mechanic_equipmentCabinet.Item, true);
bool firstSlotRemoved = false;
bool secondSlotRemoved = false;
@@ -296,7 +296,7 @@ namespace Barotrauma.Tutorials
// Room 3
do { yield return null; } while (!mechanic_weldingObjectiveSensor.MotionDetected);
TriggerTutorialSegment(2, GameMain.Config.KeyBind(InputType.Aim), GameMain.Config.KeyBind(InputType.Shoot)); // Welding objective
TriggerTutorialSegment(2, GameMain.Config.KeyBindText(InputType.Aim), GameMain.Config.KeyBindText(InputType.Shoot), GameMain.Config.KeyBindText(InputType.ToggleInventory)); // Welding objective
do
{
if (!mechanic.HasEquippedItem("divingmask"))
@@ -316,16 +316,16 @@ namespace Barotrauma.Tutorials
mechanic.RemoveActiveObjectiveEntity(mechanic_brokenWall_1);
RemoveCompletedObjective(segments[2]);
yield return new WaitForSeconds(1f, false);
TriggerTutorialSegment(3, GameMain.Config.KeyBind(InputType.Select)); // Pump objective
TriggerTutorialSegment(3, GameMain.Config.KeyBindText(InputType.Select)); // Pump objective
SetHighlight(mechanic_workingPump.Item, true);
do
{
yield return null;
if (IsSelectedItem(mechanic_workingPump.Item))
{
if (mechanic_workingPump.IsActiveSlider.FlashTimer <= 0)
if (mechanic_workingPump.PowerButton.FlashTimer <= 0)
{
mechanic_workingPump.IsActiveSlider.Flash(uiHighlightColor, 1.5f, true);
mechanic_workingPump.PowerButton.Flash(uiHighlightColor, 1.5f, true);
}
}
} while (mechanic_workingPump.FlowPercentage >= 0 || !mechanic_workingPump.IsActive); // Highlight until draining
@@ -348,6 +348,9 @@ namespace Barotrauma.Tutorials
TriggerTutorialSegment(4); // Deconstruct
SetHighlight(mechanic_craftingCabinet.Item, true);
bool gotOxygenTank = false;
bool gotSodium = false;
do
{
if (mechanic.SelectedConstruction == mechanic_craftingCabinet.Item)
@@ -381,8 +384,18 @@ namespace Barotrauma.Tutorials
}
}
}
if (!gotOxygenTank && mechanic.Inventory.FindItemByIdentifier("oxygentank") != null)
{
gotOxygenTank = true;
}
if (!gotSodium && mechanic.Inventory.FindItemByIdentifier("sodium") != null)
{
gotSodium = true;
}
yield return null;
} while ((mechanic.Inventory.FindItemByIdentifier("oxygentank") == null && mechanic.Inventory.FindItemByIdentifier("aluminium") == null) || mechanic.Inventory.FindItemByIdentifier("sodium") == null); // Wait until looted
} while (!gotOxygenTank || !gotSodium); // Wait until looted
yield return new WaitForSeconds(1.0f, false);
SetHighlight(mechanic_craftingCabinet.Item, false);
SetHighlight(mechanic_deconstructor.Item, true);
@@ -424,7 +437,9 @@ namespace Barotrauma.Tutorials
}
}
yield return null;
} while (mechanic.Inventory.FindItemByIdentifier("aluminium") == null); // Wait until deconstructed
} while (
mechanic.Inventory.FindItemByIdentifier("aluminium") == null &&
mechanic_fabricator.InputContainer.Inventory.FindItemByIdentifier("aluminium") == null); // Wait until aluminium obtained
SetHighlight(mechanic_deconstructor.Item, false);
RemoveCompletedObjective(segments[4]);
@@ -482,7 +497,7 @@ namespace Barotrauma.Tutorials
// Room 5
do { yield return null; } while (!mechanic_fireSensor.MotionDetected);
TriggerTutorialSegment(6, GameMain.Config.KeyBind(InputType.Aim), GameMain.Config.KeyBind(InputType.Shoot)); // Using the extinguisher
TriggerTutorialSegment(6, GameMain.Config.KeyBindText(InputType.Aim), GameMain.Config.KeyBindText(InputType.Shoot)); // Using the extinguisher
do { yield return null; } while (!mechanic_fire.Removed); // Wait until extinguished
yield return new WaitForSeconds(3f, false);
RemoveCompletedObjective(segments[6]);
@@ -528,7 +543,7 @@ namespace Barotrauma.Tutorials
mechanic.RemoveActiveObjectiveEntity(mechanic_brokenWall_2);
yield return new WaitForSeconds(2f, false);
TriggerTutorialSegment(9, GameMain.Config.KeyBind(InputType.Use)); // Repairing machinery (pump)
TriggerTutorialSegment(9, GameMain.Config.KeyBindText(InputType.Use)); // Repairing machinery (pump)
SetHighlight(mechanic_brokenPump.Item, true);
mechanic_brokenPump.CanBeSelected = true;
Repairable repairablePumpComponent = mechanic_brokenPump.Item.GetComponent<Repairable>();
@@ -554,9 +569,9 @@ namespace Barotrauma.Tutorials
{
if (IsSelectedItem(mechanic_brokenPump.Item))
{
if (mechanic_brokenPump.IsActiveSlider.FlashTimer <= 0)
if (mechanic_brokenPump.PowerButton.FlashTimer <= 0)
{
mechanic_brokenPump.IsActiveSlider.Flash(uiHighlightColor, 1.5f, true);
mechanic_brokenPump.PowerButton.Flash(uiHighlightColor, 1.5f, true);
}
}
}

View File

@@ -89,19 +89,34 @@ namespace Barotrauma.Tutorials
radioSpeakerName = TextManager.Get("Tutorial.Radio.Speaker");
officer = Character.Controlled;
var handcuffs = officer.Inventory.FindItemByIdentifier("handcuffs");
var handcuffs = FindOrGiveItem(officer, "handcuffs");
handcuffs.Unequip(officer);
officer.Inventory.RemoveItem(handcuffs);
var stunbaton = officer.Inventory.FindItemByIdentifier("stunbaton");
var stunbaton = FindOrGiveItem(officer, "stunbaton");
stunbaton.Unequip(officer);
officer.Inventory.RemoveItem(stunbaton);
var ballistichelmet = officer.Inventory.FindItemByIdentifier("ballistichelmet");
var smg = FindOrGiveItem(officer, "smg");
smg.Unequip(officer);
officer.Inventory.RemoveItem(smg);
var divingknife = FindOrGiveItem(officer, "divingknife");
divingknife.Unequip(officer);
officer.Inventory.RemoveItem(divingknife);
var steroids = FindOrGiveItem(officer, "steroids");
steroids.Unequip(officer);
officer.Inventory.RemoveItem(steroids);
var ballistichelmet =
officer.Inventory.FindItemByIdentifier("ballistichelmet1") ??
officer.Inventory.FindItemByIdentifier("ballistichelmet2") ??
FindOrGiveItem(officer, "ballistichelmet3");
ballistichelmet.Unequip(officer);
officer.Inventory.RemoveItem(ballistichelmet);
var bodyarmor = officer.Inventory.FindItemByIdentifier("bodyarmor");
var bodyarmor = FindOrGiveItem(officer, "bodyarmor");
bodyarmor.Unequip(officer);
officer.Inventory.RemoveItem(bodyarmor);
@@ -109,6 +124,11 @@ namespace Barotrauma.Tutorials
officer_gunIcon = gunOrder.SymbolSprite;
officer_gunIconColor = gunOrder.Color;
var bandage = FindOrGiveItem(officer, "antibleeding1");
bandage.Unequip(officer);
officer.Inventory.RemoveItem(bandage);
FindOrGiveItem(officer, "antibleeding1");
// Other tutorial items
tutorial_mechanicFinalDoorLight = Item.ItemList.Find(i => i.HasTag("tutorial_mechanicfinaldoorlight")).GetComponent<LightComponent>();
tutorial_submarineSteering = Item.ItemList.Find(i => i.HasTag("command")).GetComponent<Steering>();
@@ -185,6 +205,8 @@ namespace Barotrauma.Tutorials
{
while (GameMain.Instance.LoadingScreenOpen) yield return null;
yield return new WaitForSeconds(0.01f);
// Room 1
SoundPlayer.PlayDamageSound("StructureBlunt", 10, Character.Controlled.WorldPosition);
while (shakeTimer > 0.0f) // Wake up, shake
@@ -238,7 +260,7 @@ namespace Barotrauma.Tutorials
//RemoveCompletedObjective(segments[0]);
SetHighlight(officer_equipmentCabinet.Item, false);
do { yield return null; } while (IsSelectedItem(officer_equipmentCabinet.Item));
TriggerTutorialSegment(1, GameMain.Config.KeyBind(InputType.Aim), GameMain.Config.KeyBind(InputType.Shoot)); // Equip melee weapon & armor
TriggerTutorialSegment(1, GameMain.Config.KeyBindText(InputType.Aim), GameMain.Config.KeyBindText(InputType.Shoot)); // Equip melee weapon & armor
do
{
if (!officer.HasEquippedItem("stunbaton"))
@@ -249,12 +271,12 @@ namespace Barotrauma.Tutorials
{
HighlightInventorySlot(officer.Inventory, "bodyarmor", highlightColor, .5f, .5f, 0f);
}
if (!officer.HasEquippedItem("ballistichelmet"))
if (!officer.HasEquippedItem("ballistichelmet1"))
{
HighlightInventorySlot(officer.Inventory, "ballistichelmet", highlightColor, .5f, .5f, 0f);
HighlightInventorySlot(officer.Inventory, "ballistichelmet1", highlightColor, .5f, .5f, 0f);
}
yield return new WaitForSeconds(1f, false);
} while (!officer.HasEquippedItem("stunbaton") || !officer.HasEquippedItem("bodyarmor") || !officer.HasEquippedItem("ballistichelmet"));
} while (!officer.HasEquippedItem("stunbaton") || !officer.HasEquippedItem("bodyarmor") || !officer.HasEquippedItem("ballistichelmet1"));
RemoveCompletedObjective(segments[1]);
SetDoorAccess(officer_firstDoor, officer_firstDoorLight, true);
@@ -291,7 +313,7 @@ namespace Barotrauma.Tutorials
SetHighlight(officer_ammoShelf_2.Item, false);
RemoveCompletedObjective(segments[3]);
yield return new WaitForSeconds(2f, false);
TriggerTutorialSegment(4, GameMain.Config.KeyBind(InputType.Select), GameMain.Config.KeyBind(InputType.Shoot), GameMain.Config.KeyBind(InputType.Deselect)); // Kill hammerhead
TriggerTutorialSegment(4, GameMain.Config.KeyBindText(InputType.Select), GameMain.Config.KeyBindText(InputType.Shoot), GameMain.Config.KeyBindText(InputType.Deselect)); // Kill hammerhead
officer_hammerhead = SpawnMonster("hammerhead", officer_hammerheadSpawnPos);
officer_hammerhead.AIController.SelectTarget(officer.AiTarget);
SetHighlight(officer_coilgunPeriscope, true);
@@ -325,7 +347,7 @@ namespace Barotrauma.Tutorials
//do { yield return null; } while (!officer_rangedWeaponSensor.MotionDetected);
do { yield return null; } while (!officer_thirdDoor.IsOpen);
yield return new WaitForSeconds(3f, false);
TriggerTutorialSegment(5, GameMain.Config.KeyBind(InputType.Aim), GameMain.Config.KeyBind(InputType.Shoot)); // Ranged weapons
TriggerTutorialSegment(5, GameMain.Config.KeyBindText(InputType.Aim), GameMain.Config.KeyBindText(InputType.Shoot)); // Ranged weapons
SetHighlight(officer_rangedWeaponHolder.Item, true);
do { yield return null; } while (!officer_rangedWeaponHolder.Inventory.IsEmpty()); // Wait until looted
SetHighlight(officer_rangedWeaponHolder.Item, false);

View File

@@ -31,8 +31,8 @@ namespace Barotrauma.Tutorials
protected Color highlightColor = Color.OrangeRed;
protected Color uiHighlightColor = new Color(150, 50, 0);
protected Color buttonHighlightColor = new Color(255, 100, 0);
protected Color inaccessibleColor = Color.Red;
protected Color accessibleColor = Color.Green;
protected Color inaccessibleColor = GUI.Style.Red;
protected Color accessibleColor = GUI.Style.Green;
public ScenarioTutorial(XElement element) : base(element)
{
@@ -88,7 +88,7 @@ namespace Barotrauma.Tutorials
GameMain.GameSession.StartRound(levelSeed);
}
GameMain.GameSession.EventManager.Events.Clear();
GameMain.GameSession.EventManager.ActiveEvents.Clear();
GameMain.GameSession.EventManager.Enabled = false;
GameMain.GameScreen.Select();
@@ -102,7 +102,7 @@ namespace Barotrauma.Tutorials
Submarine.MainSub.GodMode = true;
CharacterInfo charInfo = configElement.Element("Character") == null ?
new CharacterInfo(Character.HumanSpeciesName, "", JobPrefab.Get("engineer")) :
new CharacterInfo(CharacterPrefab.HumanSpeciesName, "", JobPrefab.Get("engineer")) :
new CharacterInfo(configElement.Element("Character"));
WayPoint wayPoint = GetSpawnPoint(charInfo);
@@ -298,5 +298,16 @@ namespace Barotrauma.Tutorials
character.Bloodloss = 0.0f;
character.SetStun(0.0f, true);
}
protected Item FindOrGiveItem(Character character, string identifier)
{
var item = character.Inventory.FindItemByIdentifier(identifier);
if (item != null && !item.Removed) { return item; }
ItemPrefab itemPrefab = MapEntityPrefab.Find(name: null, identifier: identifier) as ItemPrefab;
item = new Item(itemPrefab, Vector2.Zero, submarine: null);
character.Inventory.TryPutItem(item, character, item.AllowedSlots);
return item;
}
}
}

View File

@@ -99,9 +99,9 @@ namespace Barotrauma.Tutorials
public static void Init()
{
Tutorials = new List<Tutorial>();
foreach (string file in GameMain.Instance.GetFilesOfType(ContentType.Tutorials))
foreach (ContentFile file in GameMain.Instance.GetFilesOfType(ContentType.Tutorials))
{
XDocument doc = XMLExtensions.TryLoadXml(file);
XDocument doc = XMLExtensions.TryLoadXml(file.Path);
if (doc?.Root == null) continue;
foreach (XElement element in doc.Root.Elements())
@@ -372,9 +372,10 @@ namespace Barotrauma.Tutorials
private void CreateObjectiveGUI(TutorialSegment segment, int index, TutorialContentTypes type)
{
Point replayButtonSize = new Point((int)(GUI.ObjectiveNameFont.MeasureString(segment.Objective).X), (int)(GUI.ObjectiveNameFont.MeasureString(segment.Objective).Y * 1.45f));
string objectiveText = TextManager.ParseInputTypes(segment.Objective);
Point replayButtonSize = new Point((int)(GUI.LargeFont.MeasureString(objectiveText).X), (int)(GUI.LargeFont.MeasureString(objectiveText).Y * 1.45f));
segment.ReplayButton = new GUIButton(new RectTransform(replayButtonSize, objectiveFrame.RectTransform, Anchor.TopRight, Pivot.TopRight) { AbsoluteOffset = new Point(0, (replayButtonSize.Y + (int)(20f * GUI.Scale)) * index) }, style: null);
segment.ReplayButton = new GUIButton(new RectTransform(replayButtonSize, objectiveFrame.RectTransform, Anchor.TopLeft, Pivot.TopLeft) { AbsoluteOffset = new Point(0, (replayButtonSize.Y + (int)(20f * GUI.Scale)) * index) }, style: null);
segment.ReplayButton.OnClicked += (GUIButton btn, object userdata) =>
{
if (type == TutorialContentTypes.Video)
@@ -389,13 +390,15 @@ namespace Barotrauma.Tutorials
};
string objectiveTitleText = TextManager.ParseInputTypes(objectiveTranslated);
int yOffset = (int)((GUI.ObjectiveNameFont.MeasureString(objectiveTitleText).Y / 2f + 5));
segment.LinkedTitle = new GUITextBlock(new RectTransform(new Point((int)GUI.ObjectiveTitleFont.MeasureString(objectiveTitleText).X, yOffset), segment.ReplayButton.RectTransform, Anchor.CenterRight, Pivot.BottomRight) /*{ AbsoluteOffset = new Point((int)(-10 * GUI.Scale), 0) }*/,
objectiveTitleText, textColor: Color.White, font: GUI.ObjectiveTitleFont, textAlignment: Alignment.CenterRight);
int yOffset = (int)((GUI.SubHeadingFont.MeasureString(objectiveTitleText).Y + 5));
segment.LinkedTitle = new GUITextBlock(new RectTransform(new Point((int)GUI.SubHeadingFont.MeasureString(objectiveTitleText).X, yOffset), segment.ReplayButton.RectTransform, Anchor.CenterLeft, Pivot.BottomLeft) /*{ AbsoluteOffset = new Point((int)(-10 * GUI.Scale), 0) }*/,
objectiveTitleText, textColor: Color.White, font: GUI.SubHeadingFont, textAlignment: Alignment.CenterLeft)
{
ForceUpperCase = true
};
string objectiveText = TextManager.ParseInputTypes(segment.Objective);
segment.LinkedText = new GUITextBlock(new RectTransform(new Point((int)GUI.ObjectiveNameFont.MeasureString(objectiveText).X, yOffset), segment.ReplayButton.RectTransform, Anchor.CenterRight, Pivot.TopRight) /*{ AbsoluteOffset = new Point((int)(10 * GUI.Scale), 0) }*/,
objectiveText, textColor: new Color(4, 180, 108), font: GUI.ObjectiveNameFont, textAlignment: Alignment.CenterRight);
segment.LinkedText = new GUITextBlock(new RectTransform(new Point((int)GUI.LargeFont.MeasureString(objectiveText).X, yOffset), segment.ReplayButton.RectTransform, Anchor.CenterLeft, Pivot.TopLeft) /*{ AbsoluteOffset = new Point((int)(10 * GUI.Scale), 0) }*/,
objectiveText, textColor: new Color(4, 180, 108), font: GUI.LargeFont, textAlignment: Alignment.CenterLeft);
segment.LinkedTitle.Color = segment.LinkedTitle.HoverColor = segment.LinkedTitle.PressedColor = segment.LinkedTitle.SelectedColor = Color.Transparent;
segment.LinkedText.Color = segment.LinkedText.HoverColor = segment.LinkedText.PressedColor = segment.LinkedText.SelectedColor = Color.Transparent;
@@ -449,19 +452,19 @@ namespace Barotrauma.Tutorials
RectTransform rectTA;
if (objectiveTextWidth > objectiveTitleWidth)
{
rectTA = new RectTransform(new Point(checkMarkWidth, checkMarkHeight), segment.ReplayButton.RectTransform, Anchor.BottomLeft, Pivot.BottomLeft);
rectTA.AbsoluteOffset = new Point(-rectTA.Rect.Width - (int)(10 * GUI.Scale), 0);
rectTA = new RectTransform(new Point(checkMarkWidth, checkMarkHeight), segment.ReplayButton.RectTransform, Anchor.BottomRight, Pivot.BottomRight);
rectTA.AbsoluteOffset = new Point(-rectTA.Rect.Width - (int)(25 * GUI.Scale), 0);
}
else
{
rectTA = new RectTransform(new Point(checkMarkWidth, checkMarkHeight), segment.ReplayButton.RectTransform, Anchor.BottomLeft, Pivot.BottomLeft);
rectTA.AbsoluteOffset = new Point(-rectTA.Rect.Width - (int)(10 * GUI.Scale) - (objectiveTitleWidth - objectiveTextWidth), 0);
rectTA = new RectTransform(new Point(checkMarkWidth, checkMarkHeight), segment.ReplayButton.RectTransform, Anchor.BottomRight, Pivot.BottomRight);
rectTA.AbsoluteOffset = new Point(-rectTA.Rect.Width - (int)(25 * GUI.Scale) - (objectiveTitleWidth - objectiveTextWidth), 0);
}
GUIImage checkmark = new GUIImage(rectTA, "CheckMark");
checkmark.Color = checkmark.SelectedColor = checkmark.HoverColor = checkmark.PressedColor = color;
RectTransform rectTB = new RectTransform(new Vector2(1.1f, .8f), segment.LinkedText.RectTransform, Anchor.Center, Pivot.Center);
RectTransform rectTB = new RectTransform(new Vector2(1.0f, .8f), segment.LinkedText.RectTransform, Anchor.Center, Pivot.Center);
GUIImage stroke = new GUIImage(rectTB, "Stroke");
stroke.Color = stroke.SelectedColor = stroke.HoverColor = stroke.PressedColor = color;
@@ -494,14 +497,6 @@ namespace Barotrauma.Tutorials
{
if (hasButton) height += 60;
string wrappedText = ToolBox.WrapText(text, width, GUI.Font);
height += (int)(GUI.Font.MeasureString(wrappedText).Y + 50);
if (title.Length > 0)
{
height += 35;
}
Anchor anchor = Anchor.TopRight;
if (anchorStr != string.Empty)
@@ -509,12 +504,23 @@ namespace Barotrauma.Tutorials
Enum.TryParse(anchorStr, out anchor);
}
var background = new GUIFrame(new RectTransform(new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight), GUI.Canvas, Anchor.Center), "InnerFrame", new Color(0, 0, 0, 1f));
width = (int)(width * GUI.Scale);
height = (int)(height * GUI.Scale);
var infoBlock = new GUIFrame(new RectTransform(new Point((int)(width * GUI.Scale), (int)(height * GUI.Scale)), background.RectTransform, anchor) { AbsoluteOffset = new Point(20) });
infoBlock.Flash(Color.Green);
string wrappedText = ToolBox.WrapText(text, width, GUI.Font);
height += (int)GUI.Font.MeasureString(wrappedText).Y;
var infoContent = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.8f), infoBlock.RectTransform, Anchor.Center))
if (title.Length > 0)
{
height += (int)GUI.Font.MeasureString(title).Y + (int)(150 * GUI.Scale);
}
var background = new GUIFrame(new RectTransform(new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight), GUI.Canvas, Anchor.Center), style: null, Color.Black * 0.5f);
var infoBlock = new GUIFrame(new RectTransform(new Point(width, height), background.RectTransform, anchor));
infoBlock.Flash(GUI.Style.Green);
var infoContent = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.9f), infoBlock.RectTransform, Anchor.Center))
{
Stretch = true,
AbsoluteSpacing = 5
@@ -523,26 +529,35 @@ namespace Barotrauma.Tutorials
if (title.Length > 0)
{
var titleBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoContent.RectTransform),
title, font: GUI.VideoTitleFont, textAlignment: Alignment.Center, textColor: new Color(253, 174, 0));
title, font: GUI.LargeFont, textAlignment: Alignment.Center, textColor: new Color(253, 174, 0));
titleBlock.RectTransform.IsFixedSize = true;
}
var textBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoContent.RectTransform), " " + text, wrap: true);
textBlock.RectTransform.IsFixedSize = true;
List<ColorData> colorData = ColorData.GetColorData(text, out text);
GUITextBlock textBlock;
if (colorData == null)
{
textBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoContent.RectTransform), " " + text, wrap: true);
}
else
{
textBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoContent.RectTransform), colorData, " " + text, wrap: true);
}
textBlock.RectTransform.IsFixedSize = true;
infoBoxClosedCallback = callback;
if (hasButton)
{
var buttonContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.3f), infoContent.RectTransform) { MinSize = new Point(0, 30), MaxSize = new Point((int) infoContent.Rect.X, 60) }, isHorizontal: true)
var buttonContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.15f), infoContent.RectTransform), isHorizontal: true)
{
Stretch = true,
RelativeSpacing = 0.1f
};
buttonContainer.RectTransform.IsFixedSize = true;
if (showVideo != null)
{
buttonContainer.Stretch = true;
var videoButton = new GUIButton(new RectTransform(new Vector2(0.4f, 1.0f), buttonContainer.RectTransform),
TextManager.Get("Video"), style: "GUIButtonLarge")
{
@@ -553,8 +568,13 @@ namespace Barotrauma.Tutorials
}
};
}
else
{
buttonContainer.Stretch = false;
buttonContainer.ChildAnchor = Anchor.Center;
}
var okButton = new GUIButton(new RectTransform(new Vector2(0.6f, 1.0f), buttonContainer.RectTransform),
var okButton = new GUIButton(new RectTransform(new Vector2(0.4f, 1.0f), buttonContainer.RectTransform),
TextManager.Get("OK"), style: "GUIButtonLarge")
{
OnClicked = CloseInfoFrame

View File

@@ -0,0 +1,210 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System.Collections.Generic;
namespace Barotrauma
{
partial class GameSession
{
private InfoFrameTab selectedTab;
private GUIFrame infoFrame;
private readonly List<GUIButton> tabButtons = new List<GUIButton>();
private GUIFrame infoFrameContent;
public RoundSummary RoundSummary { get; private set; }
public static bool IsInfoFrameOpen => GameMain.GameSession?.infoFrame != null;
private bool ToggleInfoFrame()
{
if (GameMain.NetworkMember != null && GameMain.NetLobbyScreen != null)
{
if (GameMain.NetLobbyScreen.HeadSelectionList != null) { GameMain.NetLobbyScreen.HeadSelectionList.Visible = false; }
if (GameMain.NetLobbyScreen.JobSelectionFrame != null) { GameMain.NetLobbyScreen.JobSelectionFrame.Visible = false; }
}
if (infoFrame == null)
{
CreateInfoFrame();
SelectInfoFrameTab(null, selectedTab);
}
else
{
infoFrame = null;
}
return true;
}
public void CreateInfoFrame()
{
int width = 600, height = 400;
tabButtons.Clear();
infoFrame = new GUIFrame(new RectTransform(Vector2.One, GUI.Canvas), style: "GUIBackgroundBlocker");
var innerFrame = new GUIFrame(new RectTransform(new Vector2(0.5f, 0.35f), infoFrame.RectTransform, Anchor.Center) { MinSize = new Point(width, height), RelativeOffset = new Vector2(0.0f, 0.033f) });
var paddedFrame = new GUIFrame(new RectTransform(new Vector2(0.95f, 0.9f), innerFrame.RectTransform, Anchor.Center), style: null);
var buttonArea = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.08f), paddedFrame.RectTransform), isHorizontal: true)
{
RelativeSpacing = 0.01f
};
infoFrameContent = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.85f), paddedFrame.RectTransform) { RelativeOffset = new Vector2(0.0f, 0.08f) }, style: "InnerFrame");
var crewButton = new GUIButton(new RectTransform(new Vector2(0.2f, 1.0f), buttonArea.RectTransform), TextManager.Get("Crew"), style: "GUITabButton")
{
UserData = InfoFrameTab.Crew,
OnClicked = SelectInfoFrameTab
};
tabButtons.Add(crewButton);
var missionButton = new GUIButton(new RectTransform(new Vector2(0.2f, 1.0f), buttonArea.RectTransform), TextManager.Get("Mission"), style: "GUITabButton")
{
UserData = InfoFrameTab.Mission,
OnClicked = SelectInfoFrameTab
};
tabButtons.Add(missionButton);
if (GameMain.NetworkMember != null)
{
var myCharacterButton = new GUIButton(new RectTransform(new Vector2(0.2f, 1.0f), buttonArea.RectTransform), TextManager.Get("MyCharacter"), style: "GUITabButton")
{
UserData = InfoFrameTab.MyCharacter,
OnClicked = SelectInfoFrameTab
};
tabButtons.Add(myCharacterButton);
}
/*TODO: fix
if (GameMain.Server != null)
{
var manageButton = new GUIButton(new RectTransform(new Vector2(0.2f, 1.0f), buttonArea.RectTransform), TextManager.Get("ManagePlayers"))
{
UserData = InfoFrameTab.ManagePlayers,
OnClicked = SelectInfoFrameTab
};
}*/
}
private bool SelectInfoFrameTab(GUIButton button, object userData)
{
selectedTab = (InfoFrameTab)userData;
CreateInfoFrame();
tabButtons.ForEach(tb => tb.Selected = (InfoFrameTab)tb.UserData == selectedTab);
switch (selectedTab)
{
case InfoFrameTab.Crew:
CrewManager.CreateCrewListFrame(CrewManager.GetCharacters(), infoFrameContent);
break;
case InfoFrameTab.Mission:
CreateMissionInfo(infoFrameContent);
break;
case InfoFrameTab.MyCharacter:
if (GameMain.NetworkMember == null) { return false; }
GameMain.NetLobbyScreen.CreatePlayerFrame(infoFrameContent);
break;
case InfoFrameTab.ManagePlayers:
//TODO: fix
//GameMain.Server.ManagePlayersFrame(infoFrameContent);
break;
}
return true;
}
private void CreateMissionInfo(GUIFrame infoFrame)
{
infoFrameContent.ClearChildren();
var isTraitor = GameMain.Client?.Character?.IsTraitor ?? false;
var missionFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, isTraitor ? 0.95f : 0.45f), infoFrameContent.RectTransform))
{
RelativeSpacing = 0.05f
};
if (Mission != null)
{
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionFrame.RectTransform), Mission.Name, font: GUI.LargeFont);
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionFrame.RectTransform), TextManager.GetWithVariable("MissionReward", "[reward]", Mission.Reward.ToString()));
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionFrame.RectTransform), Mission.Description, wrap: true);
}
else
{
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionFrame.RectTransform, Anchor.TopCenter), TextManager.Get("NoMission"), font: GUI.LargeFont);
}
if (isTraitor)
{
var traitorFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.45f), infoFrameContent.RectTransform, Anchor.BottomLeft))
{
RelativeSpacing = 0.05f
};
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), traitorFrame.RectTransform), TextManager.Get("Traitors"), font: GUI.LargeFont);
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), traitorFrame.RectTransform), GameMain.Client.Character.TraitorCurrentObjective, wrap: true);
}
}
public void AddToGUIUpdateList()
{
if (GUI.DisableHUD) return;
GameMode?.AddToGUIUpdateList();
infoFrame?.AddToGUIUpdateList();
if (GameMain.NetworkMember != null)
{
GameMain.NetLobbyScreen?.HeadSelectionList?.AddToGUIUpdateList();
GameMain.NetLobbyScreen?.JobSelectionFrame?.AddToGUIUpdateList();
}
}
partial void UpdateProjSpecific(float deltaTime)
{
if (GUI.DisableHUD) return;
if (PlayerInput.KeyDown(InputType.InfoTab) &&
(GUI.KeyboardDispatcher.Subscriber == null || GUI.KeyboardDispatcher.Subscriber is GUIListBox))
{
if (infoFrame == null)
{
ToggleInfoFrame();
}
}
else if (infoFrame != null)
{
ToggleInfoFrame();
}
if (GameMain.NetworkMember != null)
{
if (GameMain.NetLobbyScreen?.HeadSelectionList != null)
{
if (PlayerInput.PrimaryMouseButtonDown() && !GUI.IsMouseOn(GameMain.NetLobbyScreen.HeadSelectionList))
{
if (GameMain.NetLobbyScreen.HeadSelectionList != null) { GameMain.NetLobbyScreen.HeadSelectionList.Visible = false; }
}
}
if (GameMain.NetLobbyScreen?.JobSelectionFrame != null)
{
if (PlayerInput.PrimaryMouseButtonDown() && !GUI.IsMouseOn(GameMain.NetLobbyScreen.JobSelectionFrame))
{
GameMain.NetLobbyScreen.JobList.Deselect();
if (GameMain.NetLobbyScreen.JobSelectionFrame != null) { GameMain.NetLobbyScreen.JobSelectionFrame.Visible = false; }
}
}
}
}
public void Draw(SpriteBatch spriteBatch)
{
if (GUI.DisableHUD) return;
GameMode?.Draw(spriteBatch);
//infoFrame?.DrawManually(spriteBatch);
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,264 @@
using Barotrauma.Extensions;
using Barotrauma.Lights;
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
namespace Barotrauma.Items.Components
{
partial class Door : Pickable, IDrawableComponent, IServerSerializable
{
private ConvexHull convexHull;
private ConvexHull convexHull2;
private float shake;
private float shakeTimer;
private Vector2 shakePos;
//openState when the vertices of the convex hull were last calculated
private float lastConvexHullState;
[Serialize("1,1", false, description: "The scale of the shadow-casting area of the door (relative to the actual size of the door).")]
public Vector2 ShadowScale
{
get;
set;
}
public Vector2 DrawSize
{
//use the extents of the item as the draw size
get { return Vector2.Zero; }
}
private Vector2[] GetConvexHullCorners(Rectangle rect)
{
Point shadowSize = rect.Size.Multiply(ShadowScale);
Vector2 center = new Vector2(rect.Center.X, rect.Y - rect.Height / 2);
Vector2[] corners = new Vector2[4];
corners[0] = center + new Vector2(-shadowSize.X, -shadowSize.Y) / 2;
corners[1] = center + new Vector2(-shadowSize.X, shadowSize.Y) / 2;
corners[2] = center + new Vector2(shadowSize.X, shadowSize.Y) / 2;
corners[3] = center + new Vector2(shadowSize.X, -shadowSize.Y) / 2;
return corners;
}
private void UpdateConvexHulls()
{
doorRect = new Rectangle(
item.Rect.Center.X - (int)(doorSprite.size.X / 2 * item.Scale),
item.Rect.Y - item.Rect.Height / 2 + (int)(doorSprite.size.Y / 2.0f * item.Scale),
(int)(doorSprite.size.X * item.Scale),
(int)(doorSprite.size.Y * item.Scale));
Rectangle rect = doorRect;
if (IsHorizontal)
{
rect.Width = (int)(rect.Width * (1.0f - openState));
}
else
{
rect.Height = (int)(rect.Height * (1.0f - openState));
}
if (Window.Height > 0 && Window.Width > 0)
{
rect.Height = -(int)(Window.Y * item.Scale);
rect.Y += (int)(doorRect.Height * openState);
rect.Height = Math.Max(rect.Height - (rect.Y - doorRect.Y), 0);
rect.Y = Math.Min(doorRect.Y, rect.Y);
if (convexHull2 != null)
{
Rectangle rect2 = doorRect;
rect2.Y += (int)(Window.Y * item.Scale - Window.Height * item.Scale);
rect2.Y += (int)(doorRect.Height * openState);
rect2.Y = Math.Min(doorRect.Y, rect2.Y);
rect2.Height = rect2.Y - (doorRect.Y - (int)(doorRect.Height * (1.0f - openState)));
if (rect2.Height == 0)
{
convexHull2.Enabled = false;
}
else
{
convexHull2.Enabled = true;
convexHull2.SetVertices(GetConvexHullCorners(rect2));
}
}
}
if (convexHull == null) return;
if (rect.Height == 0 || rect.Width == 0)
{
convexHull.Enabled = false;
}
else
{
convexHull.Enabled = true;
convexHull.SetVertices(GetConvexHullCorners(rect));
}
}
partial void UpdateProjSpecific(float deltaTime)
{
if (shakeTimer > 0.0f)
{
shakeTimer -= deltaTime;
Vector2 noisePos = new Vector2((float)PerlinNoise.CalculatePerlin(shakeTimer * 10.0f, shakeTimer * 10.0f, 0) - 0.5f, (float)PerlinNoise.CalculatePerlin(shakeTimer * 10.0f, shakeTimer * 10.0f, 0.5f) - 0.5f);
shakePos = noisePos * shake * 2.0f;
shake = Math.Min(shake, shakeTimer * 10.0f);
}
else
{
shakePos = Vector2.Zero;
}
}
public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1)
{
Color color = item.SpriteColor;
if (brokenSprite == null)
{
//broken doors turn black if no broken sprite has been configured
color *= (item.Condition / item.Prefab.Health);
color.A = 255;
}
if (stuck > 0.0f && weldedSprite != null)
{
Vector2 weldSpritePos = new Vector2(item.Rect.Center.X, item.Rect.Y - item.Rect.Height / 2.0f) + shakePos;
if (item.Submarine != null) weldSpritePos += item.Submarine.DrawPosition;
weldSpritePos.Y = -weldSpritePos.Y;
weldedSprite.Draw(spriteBatch,
weldSpritePos, item.SpriteColor * (stuck / 100.0f), scale: item.Scale);
}
if (openState >= 1.0f)
{
return;
}
if (IsHorizontal)
{
Vector2 pos = new Vector2(item.Rect.X, item.Rect.Y - item.Rect.Height / 2) + shakePos;
if (item.Submarine != null) pos += item.Submarine.DrawPosition;
pos.Y = -pos.Y;
if (brokenSprite == null || !IsBroken)
{
spriteBatch.Draw(doorSprite.Texture, pos,
new Rectangle((int) (doorSprite.SourceRect.X + doorSprite.size.X * openState),
(int) doorSprite.SourceRect.Y,
(int) (doorSprite.size.X * (1.0f - openState)), (int) doorSprite.size.Y),
color, 0.0f, doorSprite.Origin, item.Scale, SpriteEffects.None, doorSprite.Depth);
}
if (brokenSprite != null && item.Health < item.Prefab.Health)
{
Vector2 scale = scaleBrokenSprite ? new Vector2(1.0f, 1.0f - item.Health / item.Prefab.Health) : Vector2.One;
float alpha = fadeBrokenSprite ? 1.0f - item.Health / item.Prefab.Health : 1.0f;
spriteBatch.Draw(brokenSprite.Texture, pos,
new Rectangle((int)(brokenSprite.SourceRect.X + brokenSprite.size.X * openState), brokenSprite.SourceRect.Y,
(int)(brokenSprite.size.X * (1.0f - openState)), (int)brokenSprite.size.Y),
color * alpha, 0.0f, brokenSprite.Origin, scale * item.Scale, SpriteEffects.None,
brokenSprite.Depth);
}
}
else
{
Vector2 pos = new Vector2(item.Rect.Center.X, item.Rect.Y) + shakePos;
if (item.Submarine != null) pos += item.Submarine.DrawPosition;
pos.Y = -pos.Y;
if (brokenSprite == null || !IsBroken)
{
spriteBatch.Draw(doorSprite.Texture, pos,
new Rectangle(doorSprite.SourceRect.X,
(int) (doorSprite.SourceRect.Y + doorSprite.size.Y * openState),
(int) doorSprite.size.X, (int) (doorSprite.size.Y * (1.0f - openState))),
color, 0.0f, doorSprite.Origin, item.Scale, SpriteEffects.None, doorSprite.Depth);
}
if (brokenSprite != null && item.Health < item.Prefab.Health)
{
Vector2 scale = scaleBrokenSprite ? new Vector2(1.0f - item.Health / item.Prefab.Health, 1.0f) : Vector2.One;
float alpha = fadeBrokenSprite ? 1.0f - item.Health / item.Prefab.Health : 1.0f;
spriteBatch.Draw(brokenSprite.Texture, pos,
new Rectangle(brokenSprite.SourceRect.X, (int)(brokenSprite.SourceRect.Y + brokenSprite.size.Y * openState),
(int)brokenSprite.size.X, (int)(brokenSprite.size.Y * (1.0f - openState))),
color * alpha, 0.0f, brokenSprite.Origin, scale * item.Scale, SpriteEffects.None, brokenSprite.Depth);
}
}
}
partial void OnFailedToOpen()
{
if (shakeTimer <= 0.0f)
{
PlaySound(ActionType.OnFailure);
shake = 5.0f;
shakeTimer = 1.0f;
}
}
partial void SetState(bool open, bool isNetworkMessage, bool sendNetworkMessage, bool forcedOpen)
{
if ((IsStuck && !isNetworkMessage) ||
(PredictedState == null && isOpen == open) ||
(PredictedState != null && isOpen == PredictedState.Value && isOpen == open))
{
return;
}
if (GameMain.Client != null && !isNetworkMessage)
{
bool stateChanged = open != PredictedState;
//clients can "predict" that the door opens/closes when a signal is received
//the prediction will be reset after 1 second, setting the door to a state
//sent by the server, or reverting it back to its old state if no msg from server was received
PredictedState = open;
resetPredictionTimer = CorrectionDelay;
if (stateChanged) PlaySound(forcedOpen ? ActionType.OnPicked : ActionType.OnUse);
}
else
{
isOpen = open;
if (!isNetworkMessage || open != PredictedState)
{
StopPicking(null);
PlaySound(forcedOpen ? ActionType.OnPicked : ActionType.OnUse);
}
}
}
public override void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
{
base.ClientRead(type, msg, sendingTime);
bool open = msg.ReadBoolean();
bool forcedOpen = msg.ReadBoolean();
SetState(open, isNetworkMessage: true, sendNetworkMessage: false, forcedOpen: forcedOpen);
Stuck = msg.ReadRangedSingle(0.0f, 100.0f, 8);
UInt16 lastUserID = msg.ReadUInt16();
Character user = lastUserID == 0 ? null : Entity.FindEntityByID(lastUserID) as Character;
if (user != lastUser)
{
lastUser = user;
toggleCooldownTimer = ToggleCoolDown;
}
if (isStuck) { OpenState = 0.0f; }
PredictedState = null;
}
}
}

View File

@@ -20,7 +20,7 @@ namespace Barotrauma.Items.Components
partial void DischargeProjSpecific()
{
PlaySound(ActionType.OnUse, item.WorldPosition);
PlaySound(ActionType.OnUse);
foreach (Node node in nodes)
{
GameMain.ParticleManager.CreateParticle("swirlysmoke", node.WorldPosition, Vector2.Zero);

View File

@@ -0,0 +1,108 @@
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Barotrauma.Items.Components
{
partial class Holdable : IDrawableComponent
{
public Vector2 DrawSize
{
get { return item.Rect.Size.ToVector2(); }
}
public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1)
{
if (!IsActive || picker == null || !CanBeAttached() || !picker.IsKeyDown(InputType.Aim) || picker != Character.Controlled) { return; }
Vector2 gridPos = picker.Position;
Vector2 roundedGridPos = new Vector2(
MathUtils.RoundTowardsClosest(picker.Position.X, Submarine.GridSize.X),
MathUtils.RoundTowardsClosest(picker.Position.Y, Submarine.GridSize.Y));
Vector2 attachPos = GetAttachPosition(picker);
if (item.Submarine == null)
{
Structure attachTarget = Structure.GetAttachTarget(item.WorldPosition);
if (attachTarget != null)
{
if (attachTarget.Submarine != null)
{
//set to submarine-relative position
gridPos += attachTarget.Submarine.Position;
roundedGridPos += attachTarget.Submarine.Position;
attachPos += attachTarget.Submarine.Position;
}
}
}
else
{
gridPos += item.Submarine.Position;
roundedGridPos += item.Submarine.Position;
attachPos += item.Submarine.Position;
}
Submarine.DrawGrid(spriteBatch, 14, gridPos, roundedGridPos, alpha: 0.7f);
item.Sprite.Draw(
spriteBatch,
new Vector2(attachPos.X, -attachPos.Y),
item.SpriteColor * 0.5f,
0.0f, item.Scale, SpriteEffects.None, 0.0f);
GUI.DrawRectangle(spriteBatch, new Vector2(attachPos.X - 2, -attachPos.Y - 2), Vector2.One * 5, GUI.Style.Red, thickness: 3);
}
public void ClientWrite(IWriteMessage msg, object[] extraData = null)
{
if (!attachable || body == null) { return; }
Vector2 attachPos = (Vector2)extraData[2];
msg.Write(attachPos.X);
msg.Write(attachPos.Y);
}
public override void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
{
base.ClientRead(type, msg, sendingTime);
bool shouldBeAttached = msg.ReadBoolean();
Vector2 simPosition = new Vector2(msg.ReadSingle(), msg.ReadSingle());
if (!attachable)
{
DebugConsole.ThrowError("Received an attachment event for an item that's not attachable.");
return;
}
if (shouldBeAttached)
{
if (!attached)
{
Drop(false, null);
item.SetTransform(simPosition, 0.0f);
AttachToWall();
}
}
else
{
if (attached)
{
DropConnectedWires(null);
if (body != null)
{
item.body = body;
item.body.Enabled = true;
}
IsActive = false;
DeattachFromWall();
}
}
}
}
}

View File

@@ -0,0 +1,123 @@
using Barotrauma.Particles;
using FarseerPhysics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
partial class RangedWeapon : ItemComponent
{
private Sprite crosshairSprite, crosshairPointerSprite;
private Vector2 crosshairPos, crosshairPointerPos;
private float currentCrossHairScale, currentCrossHairPointerScale;
private readonly List<ParticleEmitter> particleEmitters = new List<ParticleEmitter>();
[Serialize(1.0f, false, description: "The scale of the crosshair sprite (if there is one).")]
public float CrossHairScale
{
get;
private set;
}
partial void InitProjSpecific(XElement element)
{
foreach (XElement subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "crosshair":
{
string texturePath = subElement.GetAttributeString("texture", "");
crosshairSprite = new Sprite(subElement, texturePath.Contains("/") ? "" : Path.GetDirectoryName(item.Prefab.FilePath));
}
break;
case "crosshairpointer":
{
string texturePath = subElement.GetAttributeString("texture", "");
crosshairPointerSprite = new Sprite(subElement, texturePath.Contains("/") ? "" : Path.GetDirectoryName(item.Prefab.FilePath));
}
break;
case "particleemitter":
particleEmitters.Add(new ParticleEmitter(subElement));
break;
}
}
}
public override void UpdateHUD(Character character, float deltaTime, Camera cam)
{
currentCrossHairScale = currentCrossHairPointerScale = cam == null ? 1.0f : cam.Zoom;
if (crosshairSprite != null)
{
Vector2 aimRefWorldPos = character.AimRefPosition;
if (character.Submarine != null) { aimRefWorldPos += character.Submarine.Position; }
Vector2 itemPos = cam.WorldToScreen(aimRefWorldPos);
float rotation = (item.body.Dir == 1.0f) ? item.body.Rotation : item.body.Rotation - MathHelper.Pi;
Vector2 barrelDir = new Vector2((float)Math.Cos(rotation), -(float)Math.Sin(rotation));
Vector2 mouseDiff = itemPos - PlayerInput.MousePosition;
crosshairPos = new Vector2(
MathHelper.Clamp(itemPos.X + barrelDir.X * mouseDiff.Length(), 0, GameMain.GraphicsWidth),
MathHelper.Clamp(itemPos.Y + barrelDir.Y * mouseDiff.Length(), 0, GameMain.GraphicsHeight));
float spread = GetSpread(character);
Projectile projectile = FindProjectile();
if (projectile != null)
{
spread += MathHelper.ToRadians(projectile.Spread);
}
float crossHairDist = Vector2.Distance(item.WorldPosition, cam.ScreenToWorld(crosshairPos));
float spreadDist = (float)Math.Sin(spread) * crossHairDist;
currentCrossHairPointerScale = MathHelper.Clamp(spreadDist / Math.Min(crosshairSprite.size.X, crosshairSprite.size.Y), 0.1f, 10.0f);
}
currentCrossHairScale *= CrossHairScale;
crosshairPointerPos = PlayerInput.MousePosition;
}
public override void DrawHUD(SpriteBatch spriteBatch, Character character)
{
if (crosshairSprite == null) { return; }
if (character == null || !character.IsKeyDown(InputType.Aim)) { return; }
//camera focused on some other item/device, don't draw the crosshair
if (character.ViewTarget != null && (character.ViewTarget is Item item) && item.Prefab.FocusOnSelected) { return; }
GUI.HideCursor = (crosshairSprite != null || crosshairPointerSprite != null) &&
GUI.MouseOn == null && !Inventory.IsMouseOnInventory() && !GameMain.Instance.Paused;
if (GUI.HideCursor)
{
crosshairSprite?.Draw(spriteBatch, crosshairPos, Color.White, 0, currentCrossHairScale);
crosshairPointerSprite?.Draw(spriteBatch, crosshairPointerPos, 0, currentCrossHairPointerScale);
}
}
partial void LaunchProjSpecific()
{
Vector2 particlePos = item.WorldPosition + ConvertUnits.ToDisplayUnits(TransformedBarrelPos);
float rotation = -item.body.Rotation;
if (item.body.Dir < 0.0f) { rotation += MathHelper.Pi; }
foreach (ParticleEmitter emitter in particleEmitters)
{
emitter.Emit(1.0f, particlePos, hullGuess: null, angle: rotation, particleRotation: rotation);
}
}
protected override void RemoveComponentSpecific()
{
crosshairSprite?.Remove();
crosshairSprite = null;
crosshairPointerSprite?.Remove();
crosshairSprite = null;
}
}
}

View File

@@ -0,0 +1,530 @@
using Barotrauma.Networking;
using Barotrauma.Sounds;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
enum SoundSelectionMode
{
Random,
CharacterSpecific,
ItemSpecific,
All
}
class ItemSound
{
public readonly RoundSound RoundSound;
public readonly ActionType Type;
public string VolumeProperty;
public float VolumeMultiplier
{
get { return RoundSound.Volume; }
}
public float Range
{
get { return RoundSound.Range; }
}
public readonly bool Loop;
public ItemSound(RoundSound sound, ActionType type, bool loop = false)
{
this.RoundSound = sound;
this.Type = type;
this.Loop = loop;
}
}
partial class ItemComponent : ISerializableEntity
{
public bool HasSounds
{
get { return sounds.Count > 0; }
}
private bool[] hasSoundsOfType;
private Dictionary<ActionType, List<ItemSound>> sounds;
private Dictionary<ActionType, SoundSelectionMode> soundSelectionModes;
public GUILayoutSettings DefaultLayout { get; protected set; }
public GUILayoutSettings AlternativeLayout { get; protected set; }
public class GUILayoutSettings
{
public Vector2? RelativeSize { get; private set; }
public Point? AbsoluteSize { get; private set; }
public Vector2? RelativeOffset { get; private set; }
public Point? AbsoluteOffset { get; private set; }
public Anchor? Anchor { get; private set; }
public Pivot? Pivot { get; private set; }
public static GUILayoutSettings Load(XElement element)
{
var layout = new GUILayoutSettings();
var relativeSize = XMLExtensions.GetAttributeVector2(element, "relativesize", Vector2.Zero);
var absoluteSize = XMLExtensions.GetAttributePoint(element, "absolutesize", new Point(-1000, -1000));
var relativeOffset = XMLExtensions.GetAttributeVector2(element, "relativeoffset", Vector2.Zero);
var absoluteOffset = XMLExtensions.GetAttributePoint(element, "absoluteoffset", new Point(-1000, -1000));
if (relativeSize.Length() > 0)
{
layout.RelativeSize = relativeSize;
}
if (absoluteSize.X > 0 && absoluteSize.Y > 0)
{
layout.AbsoluteSize = absoluteSize;
}
if (relativeOffset.Length() > 0)
{
layout.RelativeOffset = relativeOffset;
}
if (absoluteOffset.X > -1000 && absoluteOffset.Y > -1000)
{
layout.AbsoluteOffset = absoluteOffset;
}
if (Enum.TryParse(XMLExtensions.GetAttributeString(element, "anchor", ""), out Anchor a))
{
layout.Anchor = a;
}
if (Enum.TryParse(XMLExtensions.GetAttributeString(element, "pivot", ""), out Pivot p))
{
layout.Pivot = p;
}
return layout;
}
public void ApplyTo(RectTransform target)
{
if (RelativeOffset.HasValue)
{
target.RelativeOffset = RelativeOffset.Value;
}
else if (AbsoluteOffset.HasValue)
{
target.AbsoluteOffset = AbsoluteOffset.Value;
}
if (RelativeSize.HasValue)
{
target.RelativeSize = RelativeSize.Value;
}
else if (AbsoluteSize.HasValue)
{
target.NonScaledSize = AbsoluteSize.Value;
}
if (Anchor.HasValue)
{
target.Anchor = Anchor.Value;
}
if (Pivot.HasValue)
{
target.Pivot = Pivot.Value;
}
else
{
target.Pivot = RectTransform.MatchPivotToAnchor(target.Anchor);
}
target.RecalculateChildren(true, true);
}
}
public GUIFrame GuiFrame { get; protected set; }
[Serialize(false, false)]
public bool AllowUIOverlap
{
get;
set;
}
private ItemComponent linkToUIComponent;
[Serialize("", false)]
public string LinkUIToComponent
{
get;
set;
}
[Serialize(0, false)]
public int HudPriority
{
get;
private set;
}
private bool useAlternativeLayout;
public bool UseAlternativeLayout
{
get { return useAlternativeLayout; }
set
{
if (AlternativeLayout != null)
{
if (value == useAlternativeLayout) { return; }
useAlternativeLayout = value;
if (useAlternativeLayout)
{
AlternativeLayout?.ApplyTo(GuiFrame.RectTransform);
}
else
{
DefaultLayout?.ApplyTo(GuiFrame.RectTransform);
}
}
}
}
private bool shouldMuffleLooping;
private float lastMuffleCheckTime;
private ItemSound loopingSound;
private SoundChannel loopingSoundChannel;
private List<SoundChannel> playingOneshotSoundChannels = new List<SoundChannel>();
public void UpdateSounds()
{
if (!isActive || item.Condition <= 0.0f)
{
StopSounds(ActionType.OnActive);
}
if (loopingSound != null && loopingSoundChannel != null && loopingSoundChannel.IsPlaying)
{
if (Timing.TotalTime > lastMuffleCheckTime + 0.2f)
{
shouldMuffleLooping = SoundPlayer.ShouldMuffleSound(Character.Controlled, item.WorldPosition, loopingSound.Range, Character.Controlled?.CurrentHull);
lastMuffleCheckTime = (float)Timing.TotalTime;
}
loopingSoundChannel.Muffled = shouldMuffleLooping;
float targetGain = GetSoundVolume(loopingSound);
float gainDiff = targetGain - loopingSoundChannel.Gain;
loopingSoundChannel.Gain += Math.Abs(gainDiff) < 0.1f ? gainDiff : Math.Sign(gainDiff) * 0.1f;
loopingSoundChannel.Position = new Vector3(item.WorldPosition, 0.0f);
}
for (int i = 0; i < playingOneshotSoundChannels.Count; i++)
{
if (!playingOneshotSoundChannels[i].IsPlaying)
{
playingOneshotSoundChannels[i].Dispose();
playingOneshotSoundChannels[i] = null;
}
}
playingOneshotSoundChannels.RemoveAll(ch => ch == null);
foreach (SoundChannel channel in playingOneshotSoundChannels)
{
channel.Position = new Vector3(item.WorldPosition, 0.0f);
}
}
public void PlaySound(ActionType type, Character user = null)
{
if (!hasSoundsOfType[(int)type]) { return; }
if (loopingSound != null)
{
float targetGain = 0.0f;
if (Vector3.DistanceSquared(GameMain.SoundManager.ListenerPosition, new Vector3(item.WorldPosition, 0.0f)) > loopingSound.Range * loopingSound.Range ||
(targetGain = GetSoundVolume(loopingSound)) <= 0.0001f)
{
if (loopingSoundChannel != null)
{
loopingSoundChannel.FadeOutAndDispose(); loopingSoundChannel = null;
}
return;
}
if (loopingSoundChannel != null && loopingSoundChannel.Sound != loopingSound.RoundSound.Sound)
{
loopingSoundChannel.FadeOutAndDispose(); loopingSoundChannel = null;
}
if (loopingSoundChannel == null || !loopingSoundChannel.IsPlaying)
{
loopingSoundChannel = loopingSound.RoundSound.Sound.Play(
new Vector3(item.WorldPosition, 0.0f),
0.01f,
SoundPlayer.ShouldMuffleSound(Character.Controlled, item.WorldPosition, loopingSound.Range, Character.Controlled?.CurrentHull));
loopingSoundChannel.Looping = true;
//TODO: tweak
loopingSoundChannel.Near = loopingSound.Range * 0.4f;
loopingSoundChannel.Far = loopingSound.Range;
}
return;
}
ItemSound itemSound = null;
var matchingSounds = sounds[type];
if (loopingSoundChannel == null || !loopingSoundChannel.IsPlaying)
{
SoundSelectionMode soundSelectionMode = soundSelectionModes[type];
int index;
if (soundSelectionMode == SoundSelectionMode.CharacterSpecific && user != null)
{
index = user.ID % matchingSounds.Count;
}
else if (soundSelectionMode == SoundSelectionMode.ItemSpecific)
{
index = item.ID % matchingSounds.Count;
}
else if (soundSelectionMode == SoundSelectionMode.All)
{
foreach (ItemSound sound in matchingSounds)
{
PlaySound(sound, item.WorldPosition, user);
}
return;
}
else
{
index = Rand.Int(matchingSounds.Count);
}
itemSound = matchingSounds[index];
PlaySound(matchingSounds[index], item.WorldPosition, user);
}
}
private void PlaySound(ItemSound itemSound, Vector2 position, Character user = null)
{
if (Vector2.DistanceSquared(new Vector2(GameMain.SoundManager.ListenerPosition.X, GameMain.SoundManager.ListenerPosition.Y), position) > itemSound.Range * itemSound.Range)
{
return;
}
if (itemSound.Loop)
{
loopingSound = itemSound;
if (loopingSoundChannel != null && loopingSoundChannel.Sound != loopingSound.RoundSound.Sound)
{
loopingSoundChannel.FadeOutAndDispose(); loopingSoundChannel = null;
}
if (loopingSoundChannel == null || !loopingSoundChannel.IsPlaying)
{
float volume = GetSoundVolume(itemSound);
if (volume <= 0.0001f) { return; }
loopingSoundChannel = loopingSound.RoundSound.Sound.Play(
new Vector3(position.X, position.Y, 0.0f),
0.01f,
muffle: SoundPlayer.ShouldMuffleSound(Character.Controlled, position, loopingSound.Range, Character.Controlled?.CurrentHull));
loopingSoundChannel.Looping = true;
//TODO: tweak
loopingSoundChannel.Near = loopingSound.Range * 0.4f;
loopingSoundChannel.Far = loopingSound.Range;
}
}
else
{
float volume = GetSoundVolume(itemSound);
if (volume <= 0.0001f) { return; }
var channel = SoundPlayer.PlaySound(itemSound.RoundSound.Sound, position, volume, itemSound.Range, item.CurrentHull);
if (channel != null) { playingOneshotSoundChannels.Add(channel); }
}
}
public void StopSounds(ActionType type)
{
if (loopingSound == null) { return; }
if (loopingSound.Type != type) { return; }
if (loopingSoundChannel != null)
{
loopingSoundChannel.FadeOutAndDispose();
loopingSoundChannel = null;
loopingSound = null;
}
}
private float GetSoundVolume(ItemSound sound)
{
if (sound == null) { return 0.0f; }
if (sound.VolumeProperty == "") { return sound.VolumeMultiplier; }
if (SerializableProperties.TryGetValue(sound.VolumeProperty, out SerializableProperty property))
{
float newVolume = 0.0f;
try
{
newVolume = (float)property.GetValue(this);
}
catch
{
return 0.0f;
}
newVolume *= sound.VolumeMultiplier;
if (!MathUtils.IsValid(newVolume))
{
DebugConsole.Log("Invalid sound volume (item " + item.Name + ", " + GetType().ToString() + "): " + newVolume);
GameAnalyticsManager.AddErrorEventOnce(
"ItemComponent.PlaySound:" + item.Name + GetType().ToString(),
GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
"Invalid sound volume (item " + item.Name + ", " + GetType().ToString() + "): " + newVolume);
return 0.0f;
}
return MathHelper.Clamp(newVolume, 0.0f, 1.0f);
}
return 0.0f;
}
public virtual bool ShouldDrawHUD(Character character)
{
return true;
}
public ItemComponent GetLinkUIToComponent()
{
if (string.IsNullOrEmpty(LinkUIToComponent))
{
return null;
}
foreach (ItemComponent component in item.Components)
{
if (component.name.ToLower() == LinkUIToComponent.ToLower())
{
linkToUIComponent = component;
}
}
if (linkToUIComponent == null)
{
DebugConsole.ThrowError("Failed to link the component \"" + Name + "\" to \"" + LinkUIToComponent + "\" in the item \"" + item.Name + "\" - component with a matching name not found.");
}
return linkToUIComponent;
}
public virtual void DrawHUD(SpriteBatch spriteBatch, Character character) { }
public virtual void AddToGUIUpdateList()
{
GuiFrame?.AddToGUIUpdateList();
}
public virtual void UpdateHUD(Character character, float deltaTime, Camera cam) { }
public virtual void CreateEditingHUD(SerializableEntityEditor editor)
{
}
private bool LoadElemProjSpecific(XElement subElement)
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "guiframe":
if (subElement.Attribute("rect") != null)
{
DebugConsole.ThrowError("Error in item config \"" + item.ConfigFile + "\" - GUIFrame defined as rect, use RectTransform instead.");
break;
}
Color? color = null;
if (subElement.Attribute("color") != null) color = subElement.GetAttributeColor("color", Color.White);
string style = subElement.Attribute("style") == null ?
null : subElement.GetAttributeString("style", "");
GuiFrame = new GUIFrame(RectTransform.Load(subElement, GUI.Canvas, Anchor.Center), style, color);
DefaultLayout = GUILayoutSettings.Load(subElement);
break;
case "alternativelayout":
AlternativeLayout = GUILayoutSettings.Load(subElement);
break;
case "itemsound":
case "sound":
string filePath = subElement.GetAttributeString("file", "");
if (filePath == "") filePath = subElement.GetAttributeString("sound", "");
if (filePath == "")
{
DebugConsole.ThrowError("Error when instantiating item \"" + item.Name + "\" - sound with no file path set");
break;
}
if (!filePath.Contains("/") && !filePath.Contains("\\") && !filePath.Contains(Path.DirectorySeparatorChar))
{
filePath = Path.Combine(Path.GetDirectoryName(item.Prefab.FilePath), filePath);
}
ActionType type;
try
{
type = (ActionType)Enum.Parse(typeof(ActionType), subElement.GetAttributeString("type", ""), true);
}
catch (Exception e)
{
DebugConsole.ThrowError("Invalid sound type in " + subElement + "!", e);
break;
}
RoundSound sound = Submarine.LoadRoundSound(subElement);
if (sound == null) { break; }
ItemSound itemSound = new ItemSound(sound, type, subElement.GetAttributeBool("loop", false))
{
VolumeProperty = subElement.GetAttributeString("volumeproperty", "").ToLowerInvariant()
};
if (soundSelectionModes == null) soundSelectionModes = new Dictionary<ActionType, SoundSelectionMode>();
if (!soundSelectionModes.ContainsKey(type) || soundSelectionModes[type] == SoundSelectionMode.Random)
{
SoundSelectionMode selectionMode = SoundSelectionMode.Random;
Enum.TryParse(subElement.GetAttributeString("selectionmode", "Random"), out selectionMode);
soundSelectionModes[type] = selectionMode;
}
List<ItemSound> soundList = null;
if (!sounds.TryGetValue(itemSound.Type, out soundList))
{
soundList = new List<ItemSound>();
sounds.Add(itemSound.Type, soundList);
hasSoundsOfType[(int)itemSound.Type] = true;
}
soundList.Add(itemSound);
break;
default:
return false; //unknown element
}
return true; //element processed
}
//Starts a coroutine that will read the correct state of the component from the NetBuffer when correctionTimer reaches zero.
protected void StartDelayedCorrection(ServerNetObject type, IReadMessage buffer, float sendingTime, bool waitForMidRoundSync = false)
{
if (delayedCorrectionCoroutine != null) CoroutineManager.StopCoroutines(delayedCorrectionCoroutine);
delayedCorrectionCoroutine = CoroutineManager.StartCoroutine(DoDelayedCorrection(type, buffer, sendingTime, waitForMidRoundSync));
}
private IEnumerable<object> DoDelayedCorrection(ServerNetObject type, IReadMessage buffer, float sendingTime, bool waitForMidRoundSync)
{
while (GameMain.Client != null &&
(correctionTimer > 0.0f || (waitForMidRoundSync && GameMain.Client.MidRoundSyncing)))
{
correctionTimer -= CoroutineManager.DeltaTime;
yield return CoroutineStatus.Running;
}
if (item.Removed || GameMain.Client == null)
{
yield return CoroutineStatus.Success;
}
((IServerSerializable)this).ClientRead(type, buffer, sendingTime);
correctionTimer = 0.0f;
delayedCorrectionCoroutine = null;
yield return CoroutineStatus.Success;
}
}
}

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