(61d00a474) v0.9.7.1
This commit is contained in:
+11
@@ -11,12 +11,23 @@ bld/
|
|||||||
[Oo]bj/
|
[Oo]bj/
|
||||||
[Dd]ebug*/
|
[Dd]ebug*/
|
||||||
[Rr]elease*/
|
[Rr]elease*/
|
||||||
|
*.o
|
||||||
|
|
||||||
# Misc vs crap
|
# Misc vs crap
|
||||||
*.v12.suo
|
*.v12.suo
|
||||||
*.suo
|
*.suo
|
||||||
*.csproj.user
|
*.csproj.user
|
||||||
*.shproj.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
|
#performance reports & sessions
|
||||||
*.vsp
|
*.vsp
|
||||||
|
|||||||
Binary file not shown.
@@ -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
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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>
|
|
||||||
@@ -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>
|
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
-2
@@ -14,8 +14,8 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public SoundType Type => Params.State;
|
public SoundType Type => Params.State;
|
||||||
public Gender Gender => Params.Gender;
|
public Gender Gender => Params.Gender;
|
||||||
public float Volume => roundSound.Volume;
|
public float Volume => roundSound == null ? 0.0f : roundSound.Volume;
|
||||||
public float Range => roundSound.Range;
|
public float Range => roundSound == null ? 0.0f : roundSound.Range;
|
||||||
public Sound Sound => roundSound?.Sound;
|
public Sound Sound => roundSound?.Sound;
|
||||||
|
|
||||||
public CharacterSound(CharacterParams.SoundParams soundParams)
|
public CharacterSound(CharacterParams.SoundParams soundParams)
|
||||||
+1
-1
@@ -41,7 +41,7 @@ namespace Barotrauma
|
|||||||
private Submarine parentSub;
|
private Submarine parentSub;
|
||||||
|
|
||||||
public HUDProgressBar(Vector2 worldPosition, Submarine parentSubmarine = null)
|
public HUDProgressBar(Vector2 worldPosition, Submarine parentSubmarine = null)
|
||||||
: this(worldPosition, parentSubmarine, Color.Red, Color.Green)
|
: this(worldPosition, parentSubmarine, GUI.Style.Red, GUI.Style.Green)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
@@ -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
+34
-8
@@ -13,8 +13,9 @@ namespace Barotrauma
|
|||||||
public OnSelectedHandler OnSelected;
|
public OnSelectedHandler OnSelected;
|
||||||
public OnSelectedHandler OnDropped;
|
public OnSelectedHandler OnDropped;
|
||||||
|
|
||||||
private GUIButton button;
|
private readonly GUIButton button;
|
||||||
private GUIListBox listBox;
|
private readonly GUIImage icon;
|
||||||
|
private readonly GUIListBox listBox;
|
||||||
|
|
||||||
private RectTransform currentHighestParent;
|
private RectTransform currentHighestParent;
|
||||||
private List<RectTransform> parentHierarchy = new List<RectTransform>();
|
private List<RectTransform> parentHierarchy = new List<RectTransform>();
|
||||||
@@ -41,7 +42,11 @@ namespace Barotrauma
|
|||||||
public bool ButtonEnabled
|
public bool ButtonEnabled
|
||||||
{
|
{
|
||||||
get { return button.Enabled; }
|
get { return button.Enabled; }
|
||||||
set { button.Enabled = value; }
|
set
|
||||||
|
{
|
||||||
|
button.Enabled = value;
|
||||||
|
if (icon != null) { icon.Enabled = value; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public GUIComponent SelectedComponent
|
public GUIComponent SelectedComponent
|
||||||
@@ -49,6 +54,7 @@ namespace Barotrauma
|
|||||||
get { return listBox.SelectedComponent; }
|
get { return listBox.SelectedComponent; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: fix implicit hiding
|
||||||
public bool Selected
|
public bool Selected
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@@ -83,6 +89,12 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Color ButtonTextColor
|
||||||
|
{
|
||||||
|
get { return button.TextColor; }
|
||||||
|
set { button.TextColor = value; }
|
||||||
|
}
|
||||||
|
|
||||||
public void ReceiveTextInput(char inputChar)
|
public void ReceiveTextInput(char inputChar)
|
||||||
{
|
{
|
||||||
GUI.KeyboardDispatcher.Subscriber = null;
|
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;
|
CanBeFocused = true;
|
||||||
|
|
||||||
this.selectMultiple = selectMultiple;
|
this.selectMultiple = selectMultiple;
|
||||||
@@ -150,14 +163,25 @@ namespace Barotrauma
|
|||||||
OnClicked = OnClicked
|
OnClicked = OnClicked
|
||||||
};
|
};
|
||||||
GUI.Style.Apply(button, "", this);
|
GUI.Style.Apply(button, "", this);
|
||||||
|
button.TextBlock.SetTextPos();
|
||||||
listBox = new GUIListBox(new RectTransform(new Point(Rect.Width, Rect.Height * MathHelper.Clamp(elementCount, 2, 10)), rectT, Anchor.BottomCenter, Pivot.TopCenter)
|
|
||||||
|
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)
|
||||||
|
|
||||||
{ IsFixedSize = false }, style: null)
|
{ IsFixedSize = false }, style: null)
|
||||||
{
|
{
|
||||||
Enabled = !selectMultiple,
|
Enabled = !selectMultiple,
|
||||||
OnSelected = SelectItem
|
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 = FindHighestParent();
|
||||||
currentHighestParent.GUIComponent.OnAddedToGUIUpdateList += AddListBoxToGUIUpdateList;
|
currentHighestParent.GUIComponent.OnAddedToGUIUpdateList += AddListBoxToGUIUpdateList;
|
||||||
@@ -243,6 +267,7 @@ namespace Barotrauma
|
|||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
button.Text = string.Join(", ", texts);
|
button.Text = string.Join(", ", texts);
|
||||||
|
// TODO: The callback is called at least twice, remove this?
|
||||||
OnSelected?.Invoke(tb.Parent, tb.Parent.UserData);
|
OnSelected?.Invoke(tb.Parent, tb.Parent.UserData);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -290,6 +315,7 @@ namespace Barotrauma
|
|||||||
button.Text = textBlock.Text;
|
button.Text = textBlock.Text;
|
||||||
}
|
}
|
||||||
Dropped = false;
|
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);
|
OnSelected?.Invoke(component, component.UserData);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -390,7 +416,7 @@ namespace Barotrauma
|
|||||||
if (!Visible) return;
|
if (!Visible) return;
|
||||||
wasOpened = false;
|
wasOpened = false;
|
||||||
base.Update(deltaTime);
|
base.Update(deltaTime);
|
||||||
if (Dropped && PlayerInput.LeftButtonClicked())
|
if (Dropped && PlayerInput.PrimaryMouseButtonClicked())
|
||||||
{
|
{
|
||||||
Rectangle listBoxRect = listBox.Rect;
|
Rectangle listBoxRect = listBox.Rect;
|
||||||
if (!listBoxRect.Contains(PlayerInput.MousePosition) && !button.Rect.Contains(PlayerInput.MousePosition))
|
if (!listBoxRect.Contains(PlayerInput.MousePosition) && !button.Rect.Contains(PlayerInput.MousePosition))
|
||||||
+2
-1
@@ -8,6 +8,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
public GUIFrame(RectTransform rectT, string style = "", Color? color = null) : base(style, rectT)
|
public GUIFrame(RectTransform rectT, string style = "", Color? color = null) : base(style, rectT)
|
||||||
{
|
{
|
||||||
|
Enabled = true;
|
||||||
if (color.HasValue)
|
if (color.HasValue)
|
||||||
{
|
{
|
||||||
this.color = color.Value;
|
this.color = color.Value;
|
||||||
@@ -18,7 +19,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
if (!Visible) return;
|
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);
|
if (sprites == null || !sprites.Any(s => s.Value.Any())) GUI.DrawRectangle(spriteBatch, Rect, currColor * (currColor.A/255.0f), true);
|
||||||
base.Draw(spriteBatch);
|
base.Draw(spriteBatch);
|
||||||
+10
-8
@@ -87,7 +87,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
if (style == null)
|
if (style == null)
|
||||||
{
|
{
|
||||||
color = hoverColor = selectedColor = pressedColor = Color.White;
|
color = hoverColor = selectedColor = pressedColor = disabledColor = Color.White;
|
||||||
}
|
}
|
||||||
if (!scaleToFit)
|
if (!scaleToFit)
|
||||||
{
|
{
|
||||||
@@ -97,15 +97,17 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
rectT.SizeChanged += RecalculateScale;
|
rectT.SizeChanged += RecalculateScale;
|
||||||
}
|
}
|
||||||
|
Enabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Draw(SpriteBatch spriteBatch)
|
protected override void Draw(SpriteBatch spriteBatch)
|
||||||
{
|
{
|
||||||
if (!Visible) return;
|
if (!Visible) return;
|
||||||
|
|
||||||
if (Parent != null) { state = Parent.State; }
|
if (Parent != null) { State = Parent.State; }
|
||||||
if (OverrideState != null) { state = OverrideState.Value; }
|
if (OverrideState != null) { State = OverrideState.Value; }
|
||||||
Color currColor = GetCurrentColor(state);
|
|
||||||
|
Color currentColor = GetColor(State);
|
||||||
|
|
||||||
if (BlendState != null)
|
if (BlendState != null)
|
||||||
{
|
{
|
||||||
@@ -115,23 +117,23 @@ namespace Barotrauma
|
|||||||
|
|
||||||
if (style != null)
|
if (style != null)
|
||||||
{
|
{
|
||||||
foreach (UISprite uiSprite in style.Sprites[state])
|
foreach (UISprite uiSprite in style.Sprites[State])
|
||||||
{
|
{
|
||||||
if (Math.Abs(Rotation) > float.Epsilon)
|
if (Math.Abs(Rotation) > float.Epsilon)
|
||||||
{
|
{
|
||||||
float scale = Math.Min(Rect.Width / uiSprite.Sprite.size.X, Rect.Height / uiSprite.Sprite.size.Y);
|
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);
|
Scale * scale, SpriteEffects, 0.0f);
|
||||||
}
|
}
|
||||||
else
|
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)
|
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);
|
Scale, SpriteEffects, 0.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+26
-9
@@ -14,6 +14,9 @@ namespace Barotrauma
|
|||||||
get { return Math.Max(400, 400 * (GameMain.GraphicsWidth / 1920)); }
|
get { return Math.Max(400, 400 * (GameMain.GraphicsWidth / 1920)); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private float inGameCloseTimer = 0.0f;
|
||||||
|
private const float inGameCloseTime = 15f;
|
||||||
|
|
||||||
public enum Type
|
public enum Type
|
||||||
{
|
{
|
||||||
Default,
|
Default,
|
||||||
@@ -87,7 +90,7 @@ namespace Barotrauma
|
|||||||
Content = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.85f), InnerFrame.RectTransform, Anchor.Center)) { AbsoluteSpacing = 5 };
|
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),
|
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);
|
GUI.Style.Apply(Header, "", this);
|
||||||
Header.RectTransform.MinSize = new Point(0, Header.Rect.Height);
|
Header.RectTransform.MinSize = new Point(0, Header.Rect.Height);
|
||||||
|
|
||||||
@@ -100,21 +103,28 @@ namespace Barotrauma
|
|||||||
Text.RectTransform.IsFixedSize = true;
|
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)),
|
var buttonContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.15f), Content.RectTransform, Anchor.BottomCenter), childAnchor: Anchor.TopCenter)
|
||||||
isHorizontal: true, childAnchor: buttons.Length > 1 ? Anchor.BottomLeft : Anchor.Center)
|
|
||||||
{
|
{
|
||||||
AbsoluteSpacing = 5,
|
AbsoluteSpacing = 5,
|
||||||
IgnoreLayoutGroups = true
|
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 =
|
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;
|
buttonContainer.RectTransform.IsFixedSize = true;
|
||||||
|
|
||||||
if (height == 0)
|
if (height == 0)
|
||||||
{
|
{
|
||||||
height += Header.Rect.Height + Content.AbsoluteSpacing;
|
height += Header.Rect.Height + Content.AbsoluteSpacing;
|
||||||
height += (Text == null ? 0 : Text.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); }
|
if (minSize.HasValue) { height = Math.Max(height, minSize.Value.Y); }
|
||||||
|
|
||||||
InnerFrame.RectTransform.NonScaledSize =
|
InnerFrame.RectTransform.NonScaledSize =
|
||||||
@@ -126,7 +136,7 @@ namespace Barotrauma
|
|||||||
Buttons = new List<GUIButton>(buttons.Length);
|
Buttons = new List<GUIButton>(buttons.Length);
|
||||||
for (int i = 0; i < buttons.Length; i++)
|
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);
|
Buttons.Add(button);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -154,7 +164,7 @@ namespace Barotrauma
|
|||||||
Buttons = new List<GUIButton>(1)
|
Buttons = new List<GUIButton>(1)
|
||||||
{
|
{
|
||||||
new GUIButton(new RectTransform(new Vector2(0.5f, 0.5f), buttonContainer.RectTransform, Anchor.Center),
|
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
|
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);
|
Text = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), Content.RectTransform), text, textAlignment: textAlignment, wrap: true);
|
||||||
GUI.Style.Apply(Text, "", this);
|
GUI.Style.Apply(Text, "", this);
|
||||||
/*Content.Recalculate();
|
Content.Recalculate();
|
||||||
Text.RectTransform.NonScaledSize = Text.RectTransform.MinSize = Text.RectTransform.MaxSize =
|
Text.RectTransform.NonScaledSize = Text.RectTransform.MinSize = Text.RectTransform.MaxSize =
|
||||||
new Point(Text.Rect.Width, Text.Rect.Height);
|
new Point(Text.Rect.Width, Text.Rect.Height);
|
||||||
Text.RectTransform.IsFixedSize = true;*/
|
Text.RectTransform.IsFixedSize = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (height == 0)
|
if (height == 0)
|
||||||
@@ -238,6 +248,13 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
InnerFrame.RectTransform.AbsoluteOffset = Vector2.SmoothStep(initialPos, defaultPos, openState).ToPoint();
|
InnerFrame.RectTransform.AbsoluteOffset = Vector2.SmoothStep(initialPos, defaultPos, openState).ToPoint();
|
||||||
openState = Math.Min(openState + deltaTime * 2.0f, 1.0f);
|
openState = Math.Min(openState + deltaTime * 2.0f, 1.0f);
|
||||||
|
|
||||||
|
inGameCloseTimer += deltaTime;
|
||||||
|
|
||||||
|
if (inGameCloseTimer >= inGameCloseTime)
|
||||||
|
{
|
||||||
|
Close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+8
-4
@@ -54,8 +54,9 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private int? selected;
|
// intentional hiding?
|
||||||
public int? Selected
|
private new int? selected;
|
||||||
|
public new int? Selected
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
@@ -64,7 +65,7 @@ namespace Barotrauma
|
|||||||
set
|
set
|
||||||
{
|
{
|
||||||
OnSelect?.Invoke(this, value);
|
OnSelect?.Invoke(this, value);
|
||||||
if (selected != null && selected.Equals(value)) return;
|
if (selected != null && selected.Equals(value)) { return; }
|
||||||
selected = value;
|
selected = value;
|
||||||
foreach (KeyValuePair<int, GUITickBox> radioButton in radioButtons)
|
foreach (KeyValuePair<int, GUITickBox> radioButton in radioButtons)
|
||||||
{
|
{
|
||||||
@@ -72,7 +73,10 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
radioButton.Value.Selected = true;
|
radioButton.Value.Selected = true;
|
||||||
}
|
}
|
||||||
else if (radioButton.Value.Selected) radioButton.Value.Selected = false;
|
else if (radioButton.Value.Selected)
|
||||||
|
{
|
||||||
|
radioButton.Value.Selected = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+49
-20
@@ -7,7 +7,10 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
public class GUIScrollBar : GUIComponent
|
public class GUIScrollBar : GUIComponent
|
||||||
{
|
{
|
||||||
public static GUIScrollBar draggingBar;
|
public static GUIScrollBar DraggingBar
|
||||||
|
{
|
||||||
|
get; private set;
|
||||||
|
}
|
||||||
|
|
||||||
private bool isHorizontal;
|
private bool isHorizontal;
|
||||||
|
|
||||||
@@ -19,11 +22,11 @@ namespace Barotrauma
|
|||||||
private float step;
|
private float step;
|
||||||
|
|
||||||
private Vector2? dragStartPos;
|
private Vector2? dragStartPos;
|
||||||
|
|
||||||
public delegate bool OnMovedHandler(GUIScrollBar scrollBar, float barScroll);
|
public delegate bool OnMovedHandler(GUIScrollBar scrollBar, float barScroll);
|
||||||
public OnMovedHandler OnMoved;
|
public OnMovedHandler OnMoved;
|
||||||
public OnMovedHandler OnReleased;
|
public OnMovedHandler OnReleased;
|
||||||
|
|
||||||
public bool IsBooleanSwitch;
|
public bool IsBooleanSwitch;
|
||||||
|
|
||||||
public override string ToolTip
|
public override string ToolTip
|
||||||
@@ -77,7 +80,11 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
enabled = value;
|
enabled = value;
|
||||||
Bar.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
|
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);
|
return ScrollToValue(this, BarScroll);
|
||||||
}
|
}
|
||||||
set
|
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);
|
else BarScroll = ValueToScroll(this, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -179,22 +186,39 @@ namespace Barotrauma
|
|||||||
public float BarSize
|
public float BarSize
|
||||||
{
|
{
|
||||||
get { return barSize; }
|
get { return barSize; }
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
barSize = Math.Min(Math.Max(value, 0.0f), 1.0f);
|
barSize = Math.Min(Math.Max(value, 0.0f), 1.0f);
|
||||||
UpdateRect();
|
UpdateRect();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public GUIScrollBar(RectTransform rectT, float barSize = 1, Color? color = null, string style = "", bool? isHorizontal = null) : base(style, rectT)
|
public GUIScrollBar(RectTransform rectT, float barSize = 1, Color? color = null, string style = "", bool? isHorizontal = null) : base(style, rectT)
|
||||||
{
|
{
|
||||||
CanBeFocused = true;
|
CanBeFocused = true;
|
||||||
|
|
||||||
this.isHorizontal = isHorizontal ?? (Rect.Width > Rect.Height);
|
this.isHorizontal = isHorizontal ?? (Rect.Width > Rect.Height);
|
||||||
Frame = new GUIFrame(new RectTransform(Vector2.One, rectT));
|
Frame = new GUIFrame(new RectTransform(Vector2.One, rectT));
|
||||||
GUI.Style.Apply(Frame, IsHorizontal ? "GUIFrameHorizontal" : "GUIFrameVertical", this);
|
GUI.Style.Apply(Frame, IsHorizontal ? "GUIFrameHorizontal" : "GUIFrameVertical", this);
|
||||||
this.barSize = barSize;
|
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);
|
GUI.Style.Apply(Bar, IsHorizontal ? "GUIButtonHorizontal" : "GUIButtonVertical", this);
|
||||||
Bar.OnPressed = SelectBar;
|
Bar.OnPressed = SelectBar;
|
||||||
enabled = true;
|
enabled = true;
|
||||||
@@ -219,12 +243,16 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
if (!Visible) { return; }
|
if (!Visible) { return; }
|
||||||
|
|
||||||
base.Update(deltaTime);
|
|
||||||
|
|
||||||
if (!enabled) { return; }
|
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 &&
|
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);
|
int dir = Math.Sign(barScroll - (minValue + maxValue) / 2.0f);
|
||||||
if (dir == 0) dir = 1;
|
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 (dragStartPos == null) { dragStartPos = PlayerInput.MousePosition; }
|
||||||
|
|
||||||
if (!PlayerInput.LeftButtonHeld())
|
if (!PlayerInput.PrimaryMouseButtonHeld())
|
||||||
{
|
{
|
||||||
if (IsBooleanSwitch && GUI.MouseOn == Bar && Vector2.Distance(dragStartPos.Value, PlayerInput.MousePosition) < 5)
|
if (IsBooleanSwitch && GUI.MouseOn == Bar && Vector2.Distance(dragStartPos.Value, PlayerInput.MousePosition) < 5)
|
||||||
{
|
{
|
||||||
@@ -247,7 +276,7 @@ namespace Barotrauma
|
|||||||
OnMoved?.Invoke(this, BarScroll);
|
OnMoved?.Invoke(this, BarScroll);
|
||||||
}
|
}
|
||||||
OnReleased?.Invoke(this, BarScroll);
|
OnReleased?.Invoke(this, BarScroll);
|
||||||
draggingBar = null;
|
DraggingBar = null;
|
||||||
dragStartPos = null;
|
dragStartPos = null;
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -259,9 +288,9 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
else if (GUI.MouseOn == Frame)
|
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)
|
if (IsBooleanSwitch)
|
||||||
{
|
{
|
||||||
MoveButton(new Vector2(
|
MoveButton(new Vector2(
|
||||||
@@ -280,10 +309,10 @@ namespace Barotrauma
|
|||||||
|
|
||||||
private bool SelectBar()
|
private bool SelectBar()
|
||||||
{
|
{
|
||||||
if (!enabled || !PlayerInput.LeftButtonDown()) { return false; }
|
if (!enabled || !PlayerInput.PrimaryMouseButtonDown()) { return false; }
|
||||||
if (barSize >= 1.0f) { return false; }
|
if (barSize >= 1.0f) { return false; }
|
||||||
|
|
||||||
draggingBar = this;
|
DraggingBar = this;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+86
-45
@@ -19,8 +19,9 @@ namespace Barotrauma
|
|||||||
bool caretVisible;
|
bool caretVisible;
|
||||||
float caretTimer;
|
float caretTimer;
|
||||||
|
|
||||||
private GUIFrame frame;
|
private readonly GUIFrame frame;
|
||||||
private GUITextBlock textBlock;
|
private readonly GUITextBlock textBlock;
|
||||||
|
private readonly GUIImage icon;
|
||||||
|
|
||||||
public Func<string, string> textFilterFunction;
|
public Func<string, string> textFilterFunction;
|
||||||
|
|
||||||
@@ -48,7 +49,6 @@ namespace Barotrauma
|
|||||||
get { return _caretIndex; }
|
get { return _caretIndex; }
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
previousCaretIndex = _caretIndex;
|
|
||||||
_caretIndex = value;
|
_caretIndex = value;
|
||||||
caretPosDirty = true;
|
caretPosDirty = true;
|
||||||
}
|
}
|
||||||
@@ -63,7 +63,6 @@ namespace Barotrauma
|
|||||||
private int selectionStartIndex;
|
private int selectionStartIndex;
|
||||||
private int selectionEndIndex;
|
private int selectionEndIndex;
|
||||||
private bool IsLeftToRight => selectionStartIndex <= selectionEndIndex;
|
private bool IsLeftToRight => selectionStartIndex <= selectionEndIndex;
|
||||||
private int previousCaretIndex;
|
|
||||||
private Vector2 selectionStartPos;
|
private Vector2 selectionStartPos;
|
||||||
private Vector2 selectionEndPos;
|
private Vector2 selectionEndPos;
|
||||||
private Vector2 selectionRectSize;
|
private Vector2 selectionRectSize;
|
||||||
@@ -76,8 +75,8 @@ namespace Barotrauma
|
|||||||
set { textBlock.TextGetter = value; }
|
set { textBlock.TextGetter = value; }
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool selected;
|
private new bool selected;
|
||||||
public bool Selected
|
public new bool Selected
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
@@ -105,6 +104,11 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public GUITextBlock TextBlock
|
||||||
|
{
|
||||||
|
get { return textBlock; }
|
||||||
|
}
|
||||||
|
|
||||||
//should the text be limited to the size of the box
|
//should the text be limited to the size of the box
|
||||||
//ignored when MaxTextLength is set or text wrapping is enabled
|
//ignored when MaxTextLength is set or text wrapping is enabled
|
||||||
public bool ClampText
|
public bool ClampText
|
||||||
@@ -134,7 +138,8 @@ namespace Barotrauma
|
|||||||
get { return enabled; }
|
get { return enabled; }
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
enabled = value;
|
enabled = frame.Enabled = textBlock.Enabled = value;
|
||||||
|
if (icon != null) { icon.Enabled = value; }
|
||||||
if (!enabled && Selected)
|
if (!enabled && Selected)
|
||||||
{
|
{
|
||||||
Deselect();
|
Deselect();
|
||||||
@@ -228,26 +233,62 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
get { return textBlock.WrappedText; }
|
get { return textBlock.WrappedText; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool Readonly { get; set; }
|
||||||
|
|
||||||
public GUITextBox(RectTransform rectT, string text = "", Color? textColor = null, ScalableFont font = null,
|
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)
|
: base(style, rectT)
|
||||||
{
|
{
|
||||||
|
HoverCursor = CursorState.IBeam;
|
||||||
CanBeFocused = true;
|
CanBeFocused = true;
|
||||||
|
|
||||||
Enabled = true;
|
|
||||||
this.color = color ?? Color.White;
|
this.color = color ?? Color.White;
|
||||||
frame = new GUIFrame(new RectTransform(Vector2.One, rectT, Anchor.Center), style, color);
|
frame = new GUIFrame(new RectTransform(Vector2.One, rectT, Anchor.Center), style, color);
|
||||||
GUI.Style.Apply(frame, style == "" ? "GUITextBox" : style);
|
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);
|
GUI.Style.Apply(textBlock, "", this);
|
||||||
CaretEnabled = true;
|
CaretEnabled = true;
|
||||||
caretPosDirty = 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;
|
Font = textBlock.Font;
|
||||||
|
|
||||||
rectT.SizeChanged += () => { caretPosDirty = true; };
|
Enabled = true;
|
||||||
rectT.ScaleChanged += () => { caretPosDirty = 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)
|
private bool SetText(string text, bool store = true)
|
||||||
@@ -417,13 +458,13 @@ namespace Barotrauma
|
|||||||
return currPosition != null ? currPosition.Item2 : textBlock.Text.Length;
|
return currPosition != null ? currPosition.Item2 : textBlock.Text.Length;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Select()
|
public void Select(int forcedCaretIndex = -1)
|
||||||
{
|
{
|
||||||
if (memento.Current == null)
|
if (memento.Current == null)
|
||||||
{
|
{
|
||||||
memento.Store(Text);
|
memento.Store(Text);
|
||||||
}
|
}
|
||||||
CaretIndex = GetCaretIndexFromScreenPos(PlayerInput.MousePosition);
|
CaretIndex = forcedCaretIndex == - 1 ? GetCaretIndexFromScreenPos(PlayerInput.MousePosition) : forcedCaretIndex;
|
||||||
ClearSelection();
|
ClearSelection();
|
||||||
selected = true;
|
selected = true;
|
||||||
GUI.KeyboardDispatcher.Subscriber = this;
|
GUI.KeyboardDispatcher.Subscriber = this;
|
||||||
@@ -442,9 +483,9 @@ namespace Barotrauma
|
|||||||
OnDeselected?.Invoke(this, Keys.None);
|
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)
|
protected override void Update(float deltaTime)
|
||||||
@@ -452,17 +493,17 @@ namespace Barotrauma
|
|||||||
if (!Visible) return;
|
if (!Visible) return;
|
||||||
|
|
||||||
if (flashTimer > 0.0f) flashTimer -= deltaTime;
|
if (flashTimer > 0.0f) flashTimer -= deltaTime;
|
||||||
if (!Enabled) return;
|
if (!Enabled) { return; }
|
||||||
if (MouseRect.Contains(PlayerInput.MousePosition) && (GUI.MouseOn == null || GUI.IsMouseOn(this)))
|
if (MouseRect.Contains(PlayerInput.MousePosition) && (GUI.MouseOn == null || (!(GUI.MouseOn is GUIButton) && GUI.IsMouseOn(this))))
|
||||||
{
|
{
|
||||||
state = ComponentState.Hover;
|
State = ComponentState.Hover;
|
||||||
if (PlayerInput.LeftButtonDown())
|
if (PlayerInput.PrimaryMouseButtonDown())
|
||||||
{
|
{
|
||||||
Select();
|
Select();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
isSelecting = PlayerInput.LeftButtonHeld();
|
isSelecting = PlayerInput.PrimaryMouseButtonHeld();
|
||||||
}
|
}
|
||||||
if (PlayerInput.DoubleClicked())
|
if (PlayerInput.DoubleClicked())
|
||||||
{
|
{
|
||||||
@@ -482,7 +523,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
if ((PlayerInput.LeftButtonClicked() || PlayerInput.RightButtonClicked()) && selected) Deselect();
|
if ((PlayerInput.LeftButtonClicked() || PlayerInput.RightButtonClicked()) && selected) Deselect();
|
||||||
isSelecting = false;
|
isSelecting = false;
|
||||||
state = ComponentState.None;
|
State = ComponentState.None;
|
||||||
}
|
}
|
||||||
if (!isSelecting)
|
if (!isSelecting)
|
||||||
{
|
{
|
||||||
@@ -493,14 +534,14 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
if (textBlock.OverflowClipActive)
|
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();
|
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();
|
CalculateCaretPos();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -514,7 +555,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
if (GUI.KeyboardDispatcher.Subscriber == this)
|
if (GUI.KeyboardDispatcher.Subscriber == this)
|
||||||
{
|
{
|
||||||
state = ComponentState.Selected;
|
State = ComponentState.Selected;
|
||||||
Character.DisableControls = true;
|
Character.DisableControls = true;
|
||||||
if (OnEnterPressed != null && PlayerInput.KeyHit(Keys.Enter))
|
if (OnEnterPressed != null && PlayerInput.KeyHit(Keys.Enter))
|
||||||
{
|
{
|
||||||
@@ -526,16 +567,12 @@ namespace Barotrauma
|
|||||||
Deselect();
|
Deselect();
|
||||||
}
|
}
|
||||||
|
|
||||||
textBlock.State = state;
|
textBlock.State = State;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Draw(SpriteBatch spriteBatch)
|
private void DrawCaretAndSelection(SpriteBatch spriteBatch, GUICustomComponent customComponent)
|
||||||
{
|
{
|
||||||
if (!Visible) return;
|
if (!Visible) { return; }
|
||||||
base.Draw(spriteBatch);
|
|
||||||
// Frame is not used in the old system.
|
|
||||||
frame?.DrawManually(spriteBatch);
|
|
||||||
textBlock.DrawManually(spriteBatch);
|
|
||||||
if (Selected)
|
if (Selected)
|
||||||
{
|
{
|
||||||
if (caretVisible )
|
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(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(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, 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, 60), $"caret index: {CaretIndex.ToString()}", GUI.Style.Red, Color.Black);
|
||||||
//GUI.DrawString(spriteBatch, new Vector2(100, 80), $"caret pos: {caretPos.ToString()}", Color.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()}", Color.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, 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);
|
//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)
|
public void ReceiveTextInput(string input)
|
||||||
{
|
{
|
||||||
|
if (Readonly) { return; }
|
||||||
if (selectedCharacters > 0)
|
if (selectedCharacters > 0)
|
||||||
{
|
{
|
||||||
RemoveSelectedText();
|
RemoveSelectedText();
|
||||||
@@ -658,7 +696,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
switch (command)
|
switch (command)
|
||||||
{
|
{
|
||||||
case '\b': //backspace
|
case '\b' when !Readonly: //backspace
|
||||||
if (PlayerInput.KeyDown(Keys.LeftControl) || PlayerInput.KeyDown(Keys.RightControl))
|
if (PlayerInput.KeyDown(Keys.LeftControl) || PlayerInput.KeyDown(Keys.RightControl))
|
||||||
{
|
{
|
||||||
SetText(string.Empty, false);
|
SetText(string.Empty, false);
|
||||||
@@ -680,7 +718,7 @@ namespace Barotrauma
|
|||||||
case (char)0x3: // ctrl-c
|
case (char)0x3: // ctrl-c
|
||||||
CopySelectedText();
|
CopySelectedText();
|
||||||
break;
|
break;
|
||||||
case (char)0x16: // ctrl-v
|
case (char)0x16 when !Readonly: // ctrl-v
|
||||||
string text = GetCopiedText();
|
string text = GetCopiedText();
|
||||||
RemoveSelectedText();
|
RemoveSelectedText();
|
||||||
if (SetText(Text.Insert(CaretIndex, text)))
|
if (SetText(Text.Insert(CaretIndex, text)))
|
||||||
@@ -691,12 +729,15 @@ namespace Barotrauma
|
|||||||
break;
|
break;
|
||||||
case (char)0x18: // ctrl-x
|
case (char)0x18: // ctrl-x
|
||||||
CopySelectedText();
|
CopySelectedText();
|
||||||
RemoveSelectedText();
|
if (!Readonly)
|
||||||
|
{
|
||||||
|
RemoveSelectedText();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case (char)0x1: // ctrl-a
|
case (char)0x1: // ctrl-a
|
||||||
SelectAll();
|
SelectAll();
|
||||||
break;
|
break;
|
||||||
case (char)0x1A: // ctrl-z
|
case (char)0x1A when !Readonly: // ctrl-z
|
||||||
text = memento.Undo();
|
text = memento.Undo();
|
||||||
if (text != Text)
|
if (text != Text)
|
||||||
{
|
{
|
||||||
@@ -706,7 +747,7 @@ namespace Barotrauma
|
|||||||
OnTextChanged?.Invoke(this, Text);
|
OnTextChanged?.Invoke(this, Text);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case (char)0x12: // ctrl-r
|
case (char)0x12 when !Readonly: // ctrl-r
|
||||||
text = memento.Redo();
|
text = memento.Redo();
|
||||||
if (text != Text)
|
if (text != Text)
|
||||||
{
|
{
|
||||||
@@ -763,7 +804,7 @@ namespace Barotrauma
|
|||||||
caretTimer = 0;
|
caretTimer = 0;
|
||||||
HandleSelection();
|
HandleSelection();
|
||||||
break;
|
break;
|
||||||
case Keys.Delete:
|
case Keys.Delete when !Readonly:
|
||||||
if (selectedCharacters > 0)
|
if (selectedCharacters > 0)
|
||||||
{
|
{
|
||||||
RemoveSelectedText();
|
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)
|
GUITextBox SelectNextTextBox(GUIListBox listBox)
|
||||||
{
|
{
|
||||||
var textBoxes = GetAndSortTextBoxes(listBox.SelectedComponent);
|
var textBoxes = GetAndSortTextBoxes(listBox.SelectedComponent);
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+3
-1
@@ -35,7 +35,9 @@ namespace Barotrauma
|
|||||||
Parent = new GUIFrame(rectT, null, Color);
|
Parent = new GUIFrame(rectT, null, Color);
|
||||||
EditorBox = new GUIListBox(new RectTransform(Vector2.One * 0.98f, rectT, Anchor.Center), color: Color.Black, style: null)
|
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;
|
return EditorBox;
|
||||||
}
|
}
|
||||||
+122
-27
@@ -23,7 +23,9 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public enum ScaleBasis
|
public enum ScaleBasis
|
||||||
{
|
{
|
||||||
Normal, BothWidth, BothHeight
|
Normal,
|
||||||
|
BothWidth, BothHeight,
|
||||||
|
Smallest, Largest
|
||||||
}
|
}
|
||||||
|
|
||||||
public class RectTransform
|
public class RectTransform
|
||||||
@@ -50,13 +52,13 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
parent.children.Add(this);
|
parent.children.Add(this);
|
||||||
RecalculateAll(false, true, true);
|
RecalculateAll(false, true, true);
|
||||||
ParentChanged?.Invoke(parent);
|
|
||||||
Parent.ChildrenChanged?.Invoke(this);
|
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 IEnumerable<RectTransform> Children => children;
|
||||||
|
|
||||||
public int CountChildren => children.Count;
|
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
|
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)
|
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);
|
Init(parent, anchor, pivot);
|
||||||
this.scaleBasis = scaleBasis;
|
_scaleBasis = scaleBasis;
|
||||||
this.relativeSize = relativeSize;
|
this.relativeSize = relativeSize;
|
||||||
this.minSize = minSize;
|
this.minSize = minSize;
|
||||||
this.maxSize = maxSize;
|
this.maxSize = maxSize;
|
||||||
@@ -342,19 +353,23 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// By default, elements defined with an absolute size (in pixels), will be treated as fixed sized.
|
/// By default, elements defined with an absolute size (in pixels) will scale with the parent.
|
||||||
/// This can be changed by setting IsFixedSize to false.
|
/// This can be changed by setting IsFixedSize to true.
|
||||||
/// </summary>
|
/// </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);
|
Init(parent, anchor, pivot);
|
||||||
this.scaleBasis = ScaleBasis.Normal;
|
_scaleBasis = scaleBasis;
|
||||||
this.nonScaledSize = absoluteSize;
|
this.nonScaledSize = absoluteSize;
|
||||||
RecalculateScale();
|
RecalculateScale();
|
||||||
RecalculateRelativeSize();
|
RecalculateRelativeSize();
|
||||||
|
if (scaleBasis != ScaleBasis.Normal)
|
||||||
|
{
|
||||||
|
RecalculateAbsoluteSize();
|
||||||
|
}
|
||||||
RecalculateAnchorPoint();
|
RecalculateAnchorPoint();
|
||||||
RecalculatePivotOffset();
|
RecalculatePivotOffset();
|
||||||
IsFixedSize = true;
|
IsFixedSize = isFixedSize;
|
||||||
parent?.ChildrenChanged?.Invoke(this);
|
parent?.ChildrenChanged?.Invoke(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -364,13 +379,24 @@ namespace Barotrauma
|
|||||||
Enum.TryParse(element.GetAttributeString("pivot", anchor.ToString()), out Pivot pivot);
|
Enum.TryParse(element.GetAttributeString("pivot", anchor.ToString()), out Pivot pivot);
|
||||||
|
|
||||||
Point? minSize = null, maxSize = null;
|
Point? minSize = null, maxSize = null;
|
||||||
if (element.Attribute("minsize") != null) minSize = element.GetAttributePoint("minsize", Point.Zero);
|
ScaleBasis scaleBasis = ScaleBasis.Normal;
|
||||||
//if (element.Attribute("maxsize") != null) maxSize = element.GetAttributePoint("maxsize", new Point(1000, 1000));
|
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;
|
RectTransform rectTransform;
|
||||||
if (element.Attribute("absolutesize") != null)
|
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,
|
minSize = minSize,
|
||||||
maxSize = maxSize
|
maxSize = maxSize
|
||||||
@@ -378,7 +404,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
else
|
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.RelativeOffset = element.GetAttributeVector2("relativeoffset", Vector2.Zero);
|
||||||
rectTransform.AbsoluteOffset = element.GetAttributePoint("absoluteoffset", Point.Zero);
|
rectTransform.AbsoluteOffset = element.GetAttributePoint("absoluteoffset", Point.Zero);
|
||||||
@@ -426,15 +452,37 @@ namespace Barotrauma
|
|||||||
protected void RecalculateAbsoluteSize()
|
protected void RecalculateAbsoluteSize()
|
||||||
{
|
{
|
||||||
Point size = NonScaledParentRect.Size;
|
Point size = NonScaledParentRect.Size;
|
||||||
if (scaleBasis == ScaleBasis.BothWidth)
|
switch (ScaleBasis)
|
||||||
{
|
{
|
||||||
size.Y = size.X;
|
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
|
||||||
|
{
|
||||||
|
size.X = size.Y;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ScaleBasis.Largest:
|
||||||
|
if (size.X > size.Y)
|
||||||
|
{
|
||||||
|
size.Y = size.X;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
size.X = size.Y;
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
else if (scaleBasis == ScaleBasis.BothHeight)
|
size = size.Multiply(RelativeSize);
|
||||||
{
|
nonScaledSize = size.Clamp(MinSize, MaxSize);
|
||||||
size.X = size.Y;
|
|
||||||
}
|
|
||||||
nonScaledSize = size.Multiply(RelativeSize).Clamp(MinSize, MaxSize);
|
|
||||||
recalculateRect = true;
|
recalculateRect = true;
|
||||||
SizeChanged?.Invoke();
|
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)
|
if (Parent == null)
|
||||||
{
|
{
|
||||||
@@ -506,7 +554,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public void Resize(Point absoluteSize, bool resizeChildren = true)
|
public void Resize(Point absoluteSize, bool resizeChildren = true)
|
||||||
{
|
{
|
||||||
nonScaledSize = absoluteSize;
|
nonScaledSize = absoluteSize.Clamp(MinSize, MaxSize);
|
||||||
RecalculateRelativeSize();
|
RecalculateRelativeSize();
|
||||||
RecalculateAll(resize: false, scale: false, withChildren: false);
|
RecalculateAll(resize: false, scale: false, withChildren: false);
|
||||||
RecalculateChildren(resizeChildren, false);
|
RecalculateChildren(resizeChildren, false);
|
||||||
@@ -573,7 +621,7 @@ namespace Barotrauma
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public IEnumerable<RectTransform> GetAllChildren()
|
public IEnumerable<RectTransform> GetAllChildren()
|
||||||
{
|
{
|
||||||
return children.SelectManyRecursive(c => c.children);
|
return children.Concat(children.SelectManyRecursive(c => c.children));
|
||||||
}
|
}
|
||||||
|
|
||||||
public int GetChildIndex(RectTransform rectT)
|
public int GetChildIndex(RectTransform rectT)
|
||||||
@@ -599,7 +647,7 @@ namespace Barotrauma
|
|||||||
public void SortChildren(Comparison<RectTransform> comparison)
|
public void SortChildren(Comparison<RectTransform> comparison)
|
||||||
{
|
{
|
||||||
children.Sort(comparison);
|
children.Sort(comparison);
|
||||||
RecalculateAll(false, true, true);
|
RecalculateAll(false, false, true);
|
||||||
Parent.ChildrenChanged?.Invoke(this);
|
Parent.ChildrenChanged?.Invoke(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -650,6 +698,53 @@ namespace Barotrauma
|
|||||||
children[i].GUIComponent.AddToGUIUpdateList(ignoreChildren, order);
|
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
|
#endregion
|
||||||
|
|
||||||
#region Static methods
|
#region Static methods
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+16
-18
@@ -20,7 +20,7 @@ namespace Barotrauma
|
|||||||
private GUICustomComponent videoView;
|
private GUICustomComponent videoView;
|
||||||
private GUIButton okButton;
|
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 Action callbackOnStop;
|
||||||
|
|
||||||
private Point scaledVideoResolution;
|
private Point scaledVideoResolution;
|
||||||
@@ -62,8 +62,8 @@ namespace Barotrauma
|
|||||||
int width = scaledVideoResolution.X;
|
int width = scaledVideoResolution.X;
|
||||||
int height = scaledVideoResolution.Y;
|
int height = scaledVideoResolution.Y;
|
||||||
|
|
||||||
background = new GUIFrame(new RectTransform(Point.Zero, GUI.Canvas, Anchor.Center), "InnerFrame", backgroundColor);
|
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), "SonarFrame");
|
videoFrame = new GUIFrame(new RectTransform(Point.Zero, background.RectTransform, Anchor.Center, Pivot.Center), style: "InnerFrame");
|
||||||
|
|
||||||
if (useTextOnRightSide)
|
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); });
|
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);
|
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");
|
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;
|
objectiveTitle.Visible = objectiveText.Visible = false;
|
||||||
}
|
}
|
||||||
@@ -111,9 +111,7 @@ namespace Barotrauma
|
|||||||
if (currentVideo == null) return;
|
if (currentVideo == null) return;
|
||||||
if (currentVideo.IsPlaying) return;
|
if (currentVideo.IsPlaying) return;
|
||||||
|
|
||||||
currentVideo.Dispose();
|
currentVideo.Play();
|
||||||
currentVideo = null;
|
|
||||||
currentVideo = CreateVideo(scaledVideoResolution);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddToGUIUpdateList(bool ignoreChildren = false, int order = 0)
|
public void AddToGUIUpdateList(bool ignoreChildren = false, int order = 0)
|
||||||
@@ -140,7 +138,7 @@ namespace Barotrauma
|
|||||||
currentVideo = null;
|
currentVideo = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
currentVideo = CreateVideo(scaledVideoResolution);
|
currentVideo = CreateVideo();
|
||||||
title.Text = textSettings != null ? TextManager.Get(contentId) : string.Empty;
|
title.Text = textSettings != null ? TextManager.Get(contentId) : string.Empty;
|
||||||
textContent.Text = textSettings != null ? textSettings.Text : string.Empty;
|
textContent.Text = textSettings != null ? textSettings.Text : string.Empty;
|
||||||
objectiveText.Text = objective;
|
objectiveText.Text = objective;
|
||||||
@@ -178,10 +176,10 @@ namespace Barotrauma
|
|||||||
|
|
||||||
background.RectTransform.NonScaledSize = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
|
background.RectTransform.NonScaledSize = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
|
||||||
|
|
||||||
videoFrame.RectTransform.NonScaledSize += scaledVideoResolution + new Point(scaledBorderSize, scaledBorderSize);
|
videoFrame.RectTransform.NonScaledSize = scaledVideoResolution + new Point(scaledBorderSize, scaledBorderSize);
|
||||||
videoView.RectTransform.NonScaledSize += scaledVideoResolution;
|
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));
|
title.RectTransform.AbsoluteOffset = new Point((int)(5 * GUI.Scale), (int)(10 * GUI.Scale));
|
||||||
|
|
||||||
if (textSettings != null && !string.IsNullOrEmpty(textSettings.Text))
|
if (textSettings != null && !string.IsNullOrEmpty(textSettings.Text))
|
||||||
@@ -189,7 +187,7 @@ namespace Barotrauma
|
|||||||
textSettings.Text = ToolBox.WrapText(textSettings.Text, scaledTextWidth, GUI.Font);
|
textSettings.Text = ToolBox.WrapText(textSettings.Text, scaledTextWidth, GUI.Font);
|
||||||
int wrappedHeight = textSettings.Text.Split('\n').Length * scaledTextHeight;
|
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)
|
if (useTextOnRightSide)
|
||||||
{
|
{
|
||||||
@@ -200,7 +198,7 @@ namespace Barotrauma
|
|||||||
textFrame.RectTransform.AbsoluteOffset = new Point(0, scaledVideoResolution.Y + scaledBorderSize * 2);
|
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);
|
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));
|
objectiveText.RectTransform.AbsoluteOffset = new Point(scaledXOffset, textContent.RectTransform.Rect.Height + objectiveTitle.Rect.Height + (int)(scaledTextHeight * 2.25f));
|
||||||
|
|
||||||
textFrame.RectTransform.NonScaledSize += new Point(0, scaledObjectiveFrameHeight);
|
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;
|
objectiveTitle.Visible = objectiveText.Visible = true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -258,13 +256,13 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Video CreateVideo(Point resolution)
|
private Video CreateVideo()
|
||||||
{
|
{
|
||||||
Video video = null;
|
Video video = null;
|
||||||
|
|
||||||
try
|
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)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
+7
-7
@@ -41,7 +41,7 @@ namespace Barotrauma
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool isFilled;
|
public bool isFilled;
|
||||||
public int inputAreaMargin;
|
public int inputAreaMargin;
|
||||||
public Color color = Color.Red;
|
public Color color = GUI.Style.Red;
|
||||||
public Color? secondaryColor;
|
public Color? secondaryColor;
|
||||||
public Color textColor = Color.White;
|
public Color textColor = Color.White;
|
||||||
public Color textBackgroundColor = Color.Black * 0.5f;
|
public Color textBackgroundColor = Color.Black * 0.5f;
|
||||||
@@ -65,7 +65,7 @@ namespace Barotrauma
|
|||||||
public object data;
|
public object data;
|
||||||
|
|
||||||
public bool IsSelected => enabled && selectedWidgets.Contains(this);
|
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);
|
public bool IsMouseOver => GUI.MouseOn == null && InputRect.Contains(PlayerInput.MousePosition);
|
||||||
private bool enabled = true;
|
private bool enabled = true;
|
||||||
public bool Enabled
|
public bool Enabled
|
||||||
@@ -111,10 +111,10 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
PreUpdate?.Invoke(deltaTime);
|
PreUpdate?.Invoke(deltaTime);
|
||||||
if (!enabled) { return; }
|
if (!enabled) { return; }
|
||||||
if (IsMouseOver || (!RequireMouseOn && selectedWidgets.Contains(this) && PlayerInput.LeftButtonHeld()))
|
if (IsMouseOver || (!RequireMouseOn && selectedWidgets.Contains(this) && PlayerInput.PrimaryMouseButtonHeld()))
|
||||||
{
|
{
|
||||||
Hovered?.Invoke();
|
Hovered?.Invoke();
|
||||||
if (RequireMouseOn || PlayerInput.LeftButtonDown())
|
if (RequireMouseOn || PlayerInput.PrimaryMouseButtonDown())
|
||||||
{
|
{
|
||||||
if ((multiselect && !selectedWidgets.Contains(this)) || selectedWidgets.None())
|
if ((multiselect && !selectedWidgets.Contains(this)) || selectedWidgets.None())
|
||||||
{
|
{
|
||||||
@@ -130,15 +130,15 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
if (IsSelected)
|
if (IsSelected)
|
||||||
{
|
{
|
||||||
if (PlayerInput.LeftButtonDown())
|
if (PlayerInput.PrimaryMouseButtonDown())
|
||||||
{
|
{
|
||||||
MouseDown?.Invoke();
|
MouseDown?.Invoke();
|
||||||
}
|
}
|
||||||
if (PlayerInput.LeftButtonHeld())
|
if (PlayerInput.PrimaryMouseButtonHeld())
|
||||||
{
|
{
|
||||||
MouseHeld?.Invoke(deltaTime);
|
MouseHeld?.Invoke(deltaTime);
|
||||||
}
|
}
|
||||||
if (PlayerInput.LeftButtonClicked())
|
if (PlayerInput.PrimaryMouseButtonClicked())
|
||||||
{
|
{
|
||||||
MouseUp?.Invoke();
|
MouseUp?.Invoke();
|
||||||
}
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+248
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+12
-7
@@ -32,11 +32,12 @@ namespace Barotrauma
|
|||||||
OnClicked = (btn, userdata) => { TryEndRound(GetLeavingSub()); return true; }
|
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++)
|
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 (Character.Controlled.Submarine != outpost) { return null; }
|
||||||
|
|
||||||
//if there's a sub docked to the outpost, we can leave the level
|
//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();
|
var dockedSub = outpost.DockedTo.FirstOrDefault();
|
||||||
return dockedSub.DockedTo.Contains(Submarine.MainSub) ? Submarine.MainSub : dockedSub;
|
return dockedSub.DockedTo.Contains(Submarine.MainSub) ? Submarine.MainSub : dockedSub;
|
||||||
@@ -292,7 +293,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
summaryScreen.RemoveChild(summaryScreen.Children.FirstOrDefault(c => c is GUIButton));
|
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"))
|
TextManager.Get("LoadGameButton"))
|
||||||
{
|
{
|
||||||
OnClicked = (GUIButton button, object obj) =>
|
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"));
|
TextManager.Get("QuitButton"));
|
||||||
quitButton.OnClicked += GameMain.LobbyScreen.QuitToMainMenu;
|
quitButton.OnClicked += GameMain.LobbyScreen.QuitToMainMenu;
|
||||||
quitButton.OnClicked += (GUIButton button, object obj) =>
|
quitButton.OnClicked += (GUIButton button, object obj) =>
|
||||||
@@ -401,14 +402,17 @@ namespace Barotrauma
|
|||||||
|
|
||||||
campaign.Money = element.GetAttributeInt("money", 0);
|
campaign.Money = element.GetAttributeInt("money", 0);
|
||||||
campaign.CheatsEnabled = element.GetAttributeBool("cheatsenabled", false);
|
campaign.CheatsEnabled = element.GetAttributeBool("cheatsenabled", false);
|
||||||
|
campaign.InitialSuppliesSpawned = element.GetAttributeBool("initialsuppliesspawned", false);
|
||||||
if (campaign.CheatsEnabled)
|
if (campaign.CheatsEnabled)
|
||||||
{
|
{
|
||||||
DebugConsole.CheatsEnabled = true;
|
DebugConsole.CheatsEnabled = true;
|
||||||
if (Steam.SteamManager.USE_STEAM && !SteamAchievementManager.CheatsEnabled)
|
#if USE_STEAM
|
||||||
|
if (!SteamAchievementManager.CheatsEnabled)
|
||||||
{
|
{
|
||||||
SteamAchievementManager.CheatsEnabled = true;
|
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.");
|
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
|
//backwards compatibility with older save files
|
||||||
@@ -429,7 +433,8 @@ namespace Barotrauma
|
|||||||
XElement modeElement = new XElement("SinglePlayerCampaign",
|
XElement modeElement = new XElement("SinglePlayerCampaign",
|
||||||
// Refunds the money when save & quitting from the map if there are items selected in the store
|
// 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("money", Money + (CargoManager != null ? CargoManager.GetTotalItemCost() : 0)),
|
||||||
new XAttribute("cheatsenabled", CheatsEnabled));
|
new XAttribute("cheatsenabled", CheatsEnabled),
|
||||||
|
new XAttribute("initialsuppliesspawned", InitialSuppliesSpawned));
|
||||||
CrewManager.Save(modeElement);
|
CrewManager.Save(modeElement);
|
||||||
Map.Save(modeElement);
|
Map.Save(modeElement);
|
||||||
element.Add(modeElement);
|
element.Add(modeElement);
|
||||||
+42
-25
@@ -1,4 +1,5 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
using Barotrauma.Items.Components;
|
using Barotrauma.Items.Components;
|
||||||
using Barotrauma.Networking;
|
using Barotrauma.Networking;
|
||||||
@@ -56,17 +57,30 @@ namespace Barotrauma.Tutorials
|
|||||||
radioSpeakerName = TextManager.Get("Tutorial.Radio.Watchman");
|
radioSpeakerName = TextManager.Get("Tutorial.Radio.Watchman");
|
||||||
GameMain.GameSession.CrewManager.AllowCharacterSwitch = false;
|
GameMain.GameSession.CrewManager.AllowCharacterSwitch = false;
|
||||||
|
|
||||||
var revolver = captain.Inventory.FindItemByIdentifier("revolver");
|
var revolver = FindOrGiveItem(captain, "revolver");
|
||||||
revolver.Unequip(captain);
|
revolver.Unequip(captain);
|
||||||
captain.Inventory.RemoveItem(revolver);
|
captain.Inventory.RemoveItem(revolver);
|
||||||
|
|
||||||
var captainscap = captain.Inventory.FindItemByIdentifier("captainscap");
|
var captainscap =
|
||||||
captainscap.Unequip(captain);
|
captain.Inventory.FindItemByIdentifier("captainscap1") ??
|
||||||
captain.Inventory.RemoveItem(captainscap);
|
captain.Inventory.FindItemByIdentifier("captainscap2") ??
|
||||||
|
captain.Inventory.FindItemByIdentifier("captainscap3");
|
||||||
|
|
||||||
var captainsuniform = captain.Inventory.FindItemByIdentifier("captainsuniform");
|
if (captainscap != null)
|
||||||
captainsuniform.Unequip(captain);
|
{
|
||||||
captain.Inventory.RemoveItem(captainsuniform);
|
captainscap.Unequip(captain);
|
||||||
|
captain.Inventory.RemoveItem(captainscap);
|
||||||
|
}
|
||||||
|
|
||||||
|
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");
|
var steerOrder = Order.GetPrefab("steer");
|
||||||
captain_steerIcon = steerOrder.SymbolSprite;
|
captain_steerIcon = steerOrder.SymbolSprite;
|
||||||
@@ -85,7 +99,7 @@ namespace Barotrauma.Tutorials
|
|||||||
captain_medicSpawnPos = Item.ItemList.Find(i => i.HasTag("captain_medicspawnpos")).WorldPosition;
|
captain_medicSpawnPos = Item.ItemList.Find(i => i.HasTag("captain_medicspawnpos")).WorldPosition;
|
||||||
tutorial_submarineDoor = Item.ItemList.Find(i => i.HasTag("tutorial_submarinedoor")).GetComponent<Door>();
|
tutorial_submarineDoor = Item.ItemList.Find(i => i.HasTag("tutorial_submarinedoor")).GetComponent<Door>();
|
||||||
tutorial_submarineDoorLight = Item.ItemList.Find(i => i.HasTag("tutorial_submarinedoorlight")).GetComponent<LightComponent>();
|
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 = Character.Create(medicInfo, captain_medicSpawnPos, "medicaldoctor");
|
||||||
captain_medic.GiveJobItems(null);
|
captain_medic.GiveJobItems(null);
|
||||||
captain_medic.CanSpeak = captain_medic.AIController.Enabled = false;
|
captain_medic.CanSpeak = captain_medic.AIController.Enabled = false;
|
||||||
@@ -107,15 +121,15 @@ namespace Barotrauma.Tutorials
|
|||||||
SetDoorAccess(tutorial_lockedDoor_1, null, false);
|
SetDoorAccess(tutorial_lockedDoor_1, null, false);
|
||||||
SetDoorAccess(tutorial_lockedDoor_2, 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 = Character.Create(mechanicInfo, WayPoint.GetRandom(SpawnType.Human, mechanicInfo.Job, Submarine.MainSub).WorldPosition, "mechanic");
|
||||||
captain_mechanic.GiveJobItems();
|
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 = Character.Create(securityInfo, WayPoint.GetRandom(SpawnType.Human, securityInfo.Job, Submarine.MainSub).WorldPosition, "securityofficer");
|
||||||
captain_security.GiveJobItems();
|
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 = Character.Create(engineerInfo, WayPoint.GetRandom(SpawnType.Human, engineerInfo.Job, Submarine.MainSub).WorldPosition, "engineer");
|
||||||
captain_engineer.GiveJobItems();
|
captain_engineer.GiveJobItems();
|
||||||
|
|
||||||
@@ -143,13 +157,14 @@ namespace Barotrauma.Tutorials
|
|||||||
do { yield return null; } while (!captain_medicObjectiveSensor.MotionDetected);
|
do { yield return null; } while (!captain_medicObjectiveSensor.MotionDetected);
|
||||||
GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(captain_medic.Info.DisplayName, TextManager.Get("Captain.Radio.Medic"), ChatMessageType.Radio, null);
|
GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(captain_medic.Info.DisplayName, TextManager.Get("Captain.Radio.Medic"), ChatMessageType.Radio, null);
|
||||||
yield return new WaitForSeconds(2f, false);
|
yield return new WaitForSeconds(2f, false);
|
||||||
GameMain.GameSession.CrewManager.ToggleCrewAreaOpen = true;
|
GameMain.GameSession.CrewManager.ToggleCrewListOpen = true;
|
||||||
GameMain.GameSession.CrewManager.AddCharacter(captain_medic);
|
GameMain.GameSession.CrewManager.AddCharacter(captain_medic);
|
||||||
TriggerTutorialSegment(0);
|
TriggerTutorialSegment(0, GameMain.Config.KeyBindText(InputType.Command));
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
yield return null;
|
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"));
|
while (!HasOrder(captain_medic, "follow"));
|
||||||
SetDoorAccess(tutorial_submarineDoor, tutorial_submarineDoorLight, true);
|
SetDoorAccess(tutorial_submarineDoor, tutorial_submarineDoorLight, true);
|
||||||
@@ -159,34 +174,37 @@ namespace Barotrauma.Tutorials
|
|||||||
do { yield return null; } while (!captain_enteredSubmarineSensor.MotionDetected);
|
do { yield return null; } while (!captain_enteredSubmarineSensor.MotionDetected);
|
||||||
yield return new WaitForSeconds(3f, false);
|
yield return new WaitForSeconds(3f, false);
|
||||||
captain_mechanic.AIController.Enabled = captain_security.AIController.Enabled = captain_engineer.AIController.Enabled = true;
|
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);
|
GameMain.GameSession.CrewManager.AddCharacter(captain_mechanic);
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
yield return null;
|
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");
|
//HighlightOrderOption("jobspecific");
|
||||||
}
|
}
|
||||||
while (!HasOrder(captain_mechanic, "repairsystems"));
|
while (!HasOrder(captain_mechanic, "repairsystems"));
|
||||||
RemoveCompletedObjective(segments[1]);
|
RemoveCompletedObjective(segments[1]);
|
||||||
yield return new WaitForSeconds(2f, false);
|
yield return new WaitForSeconds(2f, false);
|
||||||
TriggerTutorialSegment(2);
|
TriggerTutorialSegment(2, GameMain.Config.KeyBindText(InputType.Command));
|
||||||
GameMain.GameSession.CrewManager.AddCharacter(captain_security);
|
GameMain.GameSession.CrewManager.AddCharacter(captain_security);
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
yield return null;
|
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");
|
HighlightOrderOption("fireatwill");
|
||||||
}
|
}
|
||||||
while (!HasOrder(captain_security, "operateweapons", "fireatwill"));
|
while (!HasOrder(captain_security, "operateweapons", "fireatwill"));
|
||||||
RemoveCompletedObjective(segments[2]);
|
RemoveCompletedObjective(segments[2]);
|
||||||
yield return new WaitForSeconds(4f, false);
|
yield return new WaitForSeconds(4f, false);
|
||||||
TriggerTutorialSegment(3);
|
TriggerTutorialSegment(3, GameMain.Config.KeyBindText(InputType.Command));
|
||||||
GameMain.GameSession.CrewManager.AddCharacter(captain_engineer);
|
GameMain.GameSession.CrewManager.AddCharacter(captain_engineer);
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
yield return null;
|
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");
|
HighlightOrderOption("powerup");
|
||||||
}
|
}
|
||||||
while (!HasOrder(captain_engineer, "operatereactor", "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);
|
//captain_navConsoleCustomInterface.HighlightElement(0, uiHighlightColor, duration: 1.0f, pulsateAmount: 0.0f);
|
||||||
yield return new WaitForSeconds(1.0f, false);
|
yield return new WaitForSeconds(1.0f, false);
|
||||||
} while (Submarine.MainSub.DockedTo.Count > 0);
|
} while (Submarine.MainSub.DockedTo.Any());
|
||||||
RemoveCompletedObjective(segments[4]);
|
RemoveCompletedObjective(segments[4]);
|
||||||
yield return new WaitForSeconds(2f, false);
|
yield return new WaitForSeconds(2f, false);
|
||||||
TriggerTutorialSegment(5); // Navigate to destination
|
TriggerTutorialSegment(5); // Navigate to destination
|
||||||
@@ -212,10 +230,9 @@ namespace Barotrauma.Tutorials
|
|||||||
{
|
{
|
||||||
if (IsSelectedItem(captain_navConsole.Item))
|
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.SonarModeSwitch.Frame.Flash(highlightColor, 1.5f, false, false, new Vector2(2.5f, 2.5f));
|
||||||
//captain_sonar.ActiveTickBox.Box.Pulsate(Vector2.One, Vector2.One * 1.5f, 1.5f);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
yield return null;
|
yield return null;
|
||||||
@@ -229,7 +246,7 @@ namespace Barotrauma.Tutorials
|
|||||||
{
|
{
|
||||||
//captain_navConsoleCustomInterface.HighlightElement(0, uiHighlightColor, duration: 1.0f, pulsateAmount: 0.0f);
|
//captain_navConsoleCustomInterface.HighlightElement(0, uiHighlightColor, duration: 1.0f, pulsateAmount: 0.0f);
|
||||||
yield return new WaitForSeconds(1.0f, false);
|
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]);
|
RemoveCompletedObjective(segments[6]);
|
||||||
yield return new WaitForSeconds(3f, false);
|
yield return new WaitForSeconds(3f, false);
|
||||||
GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.GetWithVariable("Captain.Radio.Complete", "[OUTPOSTNAME]", GameMain.GameSession.EndLocation.Name), ChatMessageType.Radio, null);
|
GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.GetWithVariable("Captain.Radio.Complete", "[OUTPOSTNAME]", GameMain.GameSession.EndLocation.Name), ChatMessageType.Radio, null);
|
||||||
+36
-16
@@ -22,7 +22,6 @@ namespace Barotrauma.Tutorials
|
|||||||
private ItemContainer doctor_medBayCabinet;
|
private ItemContainer doctor_medBayCabinet;
|
||||||
private Character patient1, patient2;
|
private Character patient1, patient2;
|
||||||
private List<Character> subPatients;
|
private List<Character> subPatients;
|
||||||
private Hull startRoom;
|
|
||||||
private Hull medBay;
|
private Hull medBay;
|
||||||
|
|
||||||
private Door doctor_firstDoor;
|
private Door doctor_firstDoor;
|
||||||
@@ -56,6 +55,22 @@ namespace Barotrauma.Tutorials
|
|||||||
radioSpeakerName = TextManager.Get("Tutorial.Radio.Speaker");
|
radioSpeakerName = TextManager.Get("Tutorial.Radio.Speaker");
|
||||||
doctor = Character.Controlled;
|
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_suppliesCabinet = Item.ItemList.Find(i => i.HasTag("doctor_suppliescabinet"))?.GetComponent<ItemContainer>();
|
||||||
doctor_medBayCabinet = Item.ItemList.Find(i => i.HasTag("doctor_medbaycabinet"))?.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;
|
var patientHull2 = WayPoint.WayPointList.Find(wp => wp.IdCardDesc == "airlock").CurrentHull;
|
||||||
medBay = WayPoint.WayPointList.Find(wp => wp.IdCardDesc == "medbay").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 = Character.Create(assistantInfo, patientHull1.WorldPosition, "1");
|
||||||
patient1.GiveJobItems(null);
|
patient1.GiveJobItems(null);
|
||||||
patient1.CanSpeak = false;
|
patient1.CanSpeak = false;
|
||||||
patient1.AddDamage(patient1.WorldPosition, new List<Affliction>() { new Affliction(AfflictionPrefab.Burn, 45.0f) }, stun: 0, playSound: false);
|
patient1.AddDamage(patient1.WorldPosition, new List<Affliction>() { new Affliction(AfflictionPrefab.Burn, 45.0f) }, stun: 0, playSound: false);
|
||||||
patient1.AIController.Enabled = 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 = Character.Create(assistantInfo, patientHull2.WorldPosition, "2");
|
||||||
patient2.GiveJobItems(null);
|
patient2.GiveJobItems(null);
|
||||||
patient2.CanSpeak = false;
|
patient2.CanSpeak = false;
|
||||||
patient2.AIController.Enabled = 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");
|
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);
|
subPatient1.AddDamage(patient1.WorldPosition, new List<Affliction>() { new Affliction(AfflictionPrefab.Burn, 40.0f) }, stun: 0, playSound: false);
|
||||||
subPatients.Add(subPatient1);
|
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");
|
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);
|
subPatient2.AddDamage(patient1.WorldPosition, new List<Affliction>() { new Affliction(AfflictionPrefab.InternalDamage, 40.0f) }, stun: 0, playSound: false);
|
||||||
subPatients.Add(subPatient2);
|
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");
|
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);
|
subPatient3.AddDamage(patient1.WorldPosition, new List<Affliction>() { new Affliction(AfflictionPrefab.Burn, 20.0f) }, stun: 0, playSound: false);
|
||||||
subPatients.Add(subPatient3);
|
subPatients.Add(subPatient3);
|
||||||
@@ -176,7 +191,7 @@ namespace Barotrauma.Tutorials
|
|||||||
yield return new WaitForSeconds(2.0f);
|
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
|
do
|
||||||
{
|
{
|
||||||
@@ -205,7 +220,7 @@ namespace Barotrauma.Tutorials
|
|||||||
|
|
||||||
// 2nd tutorial segment, treat self -------------------------------------------------------------------------
|
// 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)
|
while (CharacterHealth.OpenHealthWindow == null)
|
||||||
{
|
{
|
||||||
doctor.CharacterHealth.HealthBarPulsateTimer = 1.0f;
|
doctor.CharacterHealth.HealthBarPulsateTimer = 1.0f;
|
||||||
@@ -255,17 +270,18 @@ namespace Barotrauma.Tutorials
|
|||||||
GameMain.GameSession.CrewManager.AllowCharacterSwitch = false;
|
GameMain.GameSession.CrewManager.AllowCharacterSwitch = false;
|
||||||
GameMain.GameSession.CrewManager.AddCharacter(doctor);
|
GameMain.GameSession.CrewManager.AddCharacter(doctor);
|
||||||
GameMain.GameSession.CrewManager.AddCharacter(patient1);
|
GameMain.GameSession.CrewManager.AddCharacter(patient1);
|
||||||
GameMain.GameSession.CrewManager.ToggleCrewAreaOpen = true;
|
GameMain.GameSession.CrewManager.ToggleCrewListOpen = true;
|
||||||
patient1.CharacterHealth.UseHealthWindow = false;
|
patient1.CharacterHealth.UseHealthWindow = false;
|
||||||
|
|
||||||
yield return new WaitForSeconds(3.0f, false);
|
yield return new WaitForSeconds(3.0f, false);
|
||||||
patient1.AIController.Enabled = true;
|
patient1.AIController.Enabled = true;
|
||||||
doctor.RemoveActiveObjectiveEntity(patient1);
|
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")
|
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;
|
yield return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -282,7 +298,7 @@ namespace Barotrauma.Tutorials
|
|||||||
|
|
||||||
yield return new WaitForSeconds(2.0f, false);
|
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
|
do
|
||||||
{
|
{
|
||||||
@@ -344,14 +360,18 @@ namespace Barotrauma.Tutorials
|
|||||||
do { yield return null; } while (!tutorial_upperFinalDoor.IsOpen);
|
do { yield return null; } while (!tutorial_upperFinalDoor.IsOpen);
|
||||||
yield return new WaitForSeconds(2.0f, false);
|
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);
|
SetHighlight(patient2, true);
|
||||||
while (patient2.IsUnconscious)
|
while (patient2.IsUnconscious)
|
||||||
{
|
{
|
||||||
if (CharacterHealth.OpenHealthWindow != null && doctor.AnimController.Anim != AnimController.Animation.CPR)
|
if (CharacterHealth.OpenHealthWindow != null && doctor.AnimController.Anim != AnimController.Animation.CPR)
|
||||||
{
|
{
|
||||||
CharacterHealth.OpenHealthWindow.CPRButton.Pulsate(Vector2.One, Vector2.One * 1.5f, 1.0f);
|
//Disabled pulse until it's replaced by a better effect
|
||||||
CharacterHealth.OpenHealthWindow.CPRButton.Flash();
|
//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;
|
yield return null;
|
||||||
}
|
}
|
||||||
@@ -374,7 +394,7 @@ namespace Barotrauma.Tutorials
|
|||||||
GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Doctor.Radio.EnteredSub"), ChatMessageType.Radio, null);
|
GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Doctor.Radio.EnteredSub"), ChatMessageType.Radio, null);
|
||||||
|
|
||||||
yield return new WaitForSeconds(3.0f, false);
|
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)
|
foreach (var patient in subPatients)
|
||||||
{
|
{
|
||||||
+15
-15
@@ -87,7 +87,7 @@ namespace Barotrauma.Tutorials
|
|||||||
radioSpeakerName = TextManager.Get("Tutorial.Radio.Speaker");
|
radioSpeakerName = TextManager.Get("Tutorial.Radio.Speaker");
|
||||||
engineer = Character.Controlled;
|
engineer = Character.Controlled;
|
||||||
|
|
||||||
var toolbox = engineer.Inventory.FindItemByIdentifier("toolbox");
|
var toolbox = FindOrGiveItem(engineer, "toolbox");
|
||||||
toolbox.Unequip(engineer);
|
toolbox.Unequip(engineer);
|
||||||
engineer.Inventory.RemoveItem(toolbox);
|
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 = Item.ItemList.Find(i => i.HasTag("engineer_reactor")).GetComponent<Reactor>();
|
||||||
engineer_reactor.FireDelay = engineer_reactor.MeltdownDelay = float.PositiveInfinity;
|
engineer_reactor.FireDelay = engineer_reactor.MeltdownDelay = float.PositiveInfinity;
|
||||||
engineer_reactor.FuelConsumptionRate = 0.0f;
|
engineer_reactor.FuelConsumptionRate = 0.0f;
|
||||||
engineer_reactor.OnOffSwitch.BarScroll = 1f;
|
engineer_reactor.PowerOn = true;
|
||||||
reactorOperatedProperly = false;
|
reactorOperatedProperly = false;
|
||||||
|
|
||||||
engineer_secondDoor = Item.ItemList.Find(i => i.HasTag("engineer_seconddoor")).GetComponent<Door>(); ;
|
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_2 = Item.ItemList.Find(i => i.HasTag("engineer_submarinejunctionbox_2"));
|
||||||
engineer_submarineJunctionBox_3 = Item.ItemList.Find(i => i.HasTag("engineer_submarinejunctionbox_3"));
|
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 = 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_submarineReactor.IsActive = engineer_submarineReactor.AutoTemp = false;
|
||||||
|
|
||||||
engineer_submarineJunctionBox_1.Indestructible = false;
|
engineer_submarineJunctionBox_1.Indestructible = false;
|
||||||
@@ -235,7 +235,7 @@ namespace Barotrauma.Tutorials
|
|||||||
do { yield return null; } while (!engineer_equipmentObjectiveSensor.MotionDetected);
|
do { yield return null; } while (!engineer_equipmentObjectiveSensor.MotionDetected);
|
||||||
GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Engineer.Radio.Equipment"), ChatMessageType.Radio, null);
|
GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Engineer.Radio.Equipment"), ChatMessageType.Radio, null);
|
||||||
yield return new WaitForSeconds(0.5f, false);
|
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 firstSlotRemoved = false;
|
||||||
bool secondSlotRemoved = false;
|
bool secondSlotRemoved = false;
|
||||||
bool thirdSlotRemoved = false;
|
bool thirdSlotRemoved = false;
|
||||||
@@ -289,19 +289,19 @@ namespace Barotrauma.Tutorials
|
|||||||
{
|
{
|
||||||
if (IsSelectedItem(engineer_reactor.Item))
|
if (IsSelectedItem(engineer_reactor.Item))
|
||||||
{
|
{
|
||||||
engineer_reactor.AutoTempSlider.BarScrollValue = 1.0f;
|
engineer_reactor.AutoTemp = false;
|
||||||
if (engineer_reactor.OnOffSwitch.FlashTimer <= 0)
|
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;
|
yield return null;
|
||||||
} while (engineer_reactor.OnOffSwitch.BarScroll > 0.45f);
|
} while (!engineer_reactor.PowerOn);
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
if (IsSelectedItem(engineer_reactor.Item) && engineer_reactor.Item.OwnInventory.slots != null)
|
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);
|
HighlightInventorySlot(engineer.Inventory, "fuelrod", highlightColor, 0.5f, 0.5f, 0f);
|
||||||
|
|
||||||
for (int i = 0; i < engineer_reactor.Item.OwnInventory.slots.Length; i++)
|
for (int i = 0; i < engineer_reactor.Item.OwnInventory.slots.Length; i++)
|
||||||
@@ -316,7 +316,7 @@ namespace Barotrauma.Tutorials
|
|||||||
{
|
{
|
||||||
if (IsSelectedItem(engineer_reactor.Item))
|
if (IsSelectedItem(engineer_reactor.Item))
|
||||||
{
|
{
|
||||||
engineer_reactor.AutoTempSlider.BarScrollValue = 1.0f;
|
engineer_reactor.AutoTemp = false;
|
||||||
if (engineer_reactor.FissionRateScrollBar.FlashTimer <= 0)
|
if (engineer_reactor.FissionRateScrollBar.FlashTimer <= 0)
|
||||||
{
|
{
|
||||||
engineer_reactor.FissionRateScrollBar.Flash(highlightColor, 1.5f);
|
engineer_reactor.FissionRateScrollBar.Flash(highlightColor, 1.5f);
|
||||||
@@ -335,9 +335,9 @@ namespace Barotrauma.Tutorials
|
|||||||
{
|
{
|
||||||
if (IsSelectedItem(engineer_reactor.Item))
|
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;
|
yield return null;
|
||||||
@@ -348,7 +348,7 @@ namespace Barotrauma.Tutorials
|
|||||||
{
|
{
|
||||||
yield return new WaitForSeconds(0.1f, false);
|
yield return new WaitForSeconds(0.1f, false);
|
||||||
wait -= 0.1f;
|
wait -= 0.1f;
|
||||||
engineer_reactor.AutoTempSlider.BarScrollValue = 0.0f;
|
engineer_reactor.AutoTemp = true;
|
||||||
} while (wait > 0.0f);
|
} while (wait > 0.0f);
|
||||||
engineer.SelectedConstruction = null;
|
engineer.SelectedConstruction = null;
|
||||||
engineer_reactor.CanBeSelected = false;
|
engineer_reactor.CanBeSelected = false;
|
||||||
@@ -361,7 +361,7 @@ namespace Barotrauma.Tutorials
|
|||||||
do { yield return null; } while (!engineer_secondDoor.IsOpen);
|
do { yield return null; } while (!engineer_secondDoor.IsOpen);
|
||||||
yield return new WaitForSeconds(1f, false);
|
yield return new WaitForSeconds(1f, false);
|
||||||
Repairable repairableJunctionBoxComponent = engineer_brokenJunctionBox.GetComponent<Repairable>();
|
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
|
do
|
||||||
{
|
{
|
||||||
if (!engineer.HasEquippedItem("screwdriver"))
|
if (!engineer.HasEquippedItem("screwdriver"))
|
||||||
@@ -389,7 +389,7 @@ namespace Barotrauma.Tutorials
|
|||||||
do { yield return null; } while (!engineer_thirdDoor.IsOpen);
|
do { yield return null; } while (!engineer_thirdDoor.IsOpen);
|
||||||
GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Engineer.Radio.FaultyWiring"), ChatMessageType.Radio, null);
|
GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Engineer.Radio.FaultyWiring"), ChatMessageType.Radio, null);
|
||||||
yield return new WaitForSeconds(2f, false);
|
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
|
do { CheckGhostWires(); HandleJunctionBoxWiringHighlights(); yield return null; } while (engineer_workingPump.Voltage < engineer_workingPump.MinVoltage); // Wait until connected all the way to the pump
|
||||||
CheckGhostWires();
|
CheckGhostWires();
|
||||||
for (int i = 0; i < engineer_disconnectedJunctionBoxes.Length; i++)
|
for (int i = 0; i < engineer_disconnectedJunctionBoxes.Length; i++)
|
||||||
+30
-15
@@ -87,18 +87,18 @@ namespace Barotrauma.Tutorials
|
|||||||
radioSpeakerName = TextManager.Get("Tutorial.Radio.Speaker");
|
radioSpeakerName = TextManager.Get("Tutorial.Radio.Speaker");
|
||||||
mechanic = Character.Controlled;
|
mechanic = Character.Controlled;
|
||||||
|
|
||||||
var toolbox = mechanic.Inventory.FindItemByIdentifier("toolbox");
|
var toolbox = FindOrGiveItem(mechanic, "toolbox");
|
||||||
toolbox.Unequip(mechanic);
|
toolbox.Unequip(mechanic);
|
||||||
mechanic.Inventory.RemoveItem(toolbox);
|
mechanic.Inventory.RemoveItem(toolbox);
|
||||||
|
|
||||||
var crowbar = mechanic.Inventory.FindItemByIdentifier("crowbar");
|
var crowbar = FindOrGiveItem(mechanic, "crowbar");
|
||||||
crowbar.Unequip(mechanic);
|
crowbar.Unequip(mechanic);
|
||||||
mechanic.Inventory.RemoveItem(crowbar);
|
mechanic.Inventory.RemoveItem(crowbar);
|
||||||
|
|
||||||
var repairOrder = Order.GetPrefab("repairsystems");
|
var repairOrder = Order.GetPrefab("repairsystems");
|
||||||
mechanic_repairIcon = repairOrder.SymbolSprite;
|
mechanic_repairIcon = repairOrder.SymbolSprite;
|
||||||
mechanic_repairIconColor = repairOrder.Color;
|
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
|
// Other tutorial items
|
||||||
tutorial_securityFinalDoorLight = Item.ItemList.Find(i => i.HasTag("tutorial_securityfinaldoorlight")).GetComponent<LightComponent>();
|
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);
|
GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Mechanic.Radio.WakeUp"), ChatMessageType.Radio, null);
|
||||||
|
|
||||||
yield return new WaitForSeconds(2.5f, false);
|
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);
|
yield return new WaitForSeconds(0.0f, false);
|
||||||
SetDoorAccess(mechanic_firstDoor, mechanic_firstDoorLight, true);
|
SetDoorAccess(mechanic_firstDoor, mechanic_firstDoorLight, true);
|
||||||
SetHighlight(mechanic_firstDoor.Item, true);
|
SetHighlight(mechanic_firstDoor.Item, true);
|
||||||
@@ -254,7 +254,7 @@ namespace Barotrauma.Tutorials
|
|||||||
yield return new WaitForSeconds(0.0f, false);
|
yield return new WaitForSeconds(0.0f, false);
|
||||||
GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Mechanic.Radio.Equipment"), ChatMessageType.Radio, null);
|
GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Mechanic.Radio.Equipment"), ChatMessageType.Radio, null);
|
||||||
do { yield return null; } while (!mechanic_equipmentObjectiveSensor.MotionDetected);
|
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);
|
SetHighlight(mechanic_equipmentCabinet.Item, true);
|
||||||
bool firstSlotRemoved = false;
|
bool firstSlotRemoved = false;
|
||||||
bool secondSlotRemoved = false;
|
bool secondSlotRemoved = false;
|
||||||
@@ -296,7 +296,7 @@ namespace Barotrauma.Tutorials
|
|||||||
|
|
||||||
// Room 3
|
// Room 3
|
||||||
do { yield return null; } while (!mechanic_weldingObjectiveSensor.MotionDetected);
|
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
|
do
|
||||||
{
|
{
|
||||||
if (!mechanic.HasEquippedItem("divingmask"))
|
if (!mechanic.HasEquippedItem("divingmask"))
|
||||||
@@ -316,16 +316,16 @@ namespace Barotrauma.Tutorials
|
|||||||
mechanic.RemoveActiveObjectiveEntity(mechanic_brokenWall_1);
|
mechanic.RemoveActiveObjectiveEntity(mechanic_brokenWall_1);
|
||||||
RemoveCompletedObjective(segments[2]);
|
RemoveCompletedObjective(segments[2]);
|
||||||
yield return new WaitForSeconds(1f, false);
|
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);
|
SetHighlight(mechanic_workingPump.Item, true);
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
yield return null;
|
yield return null;
|
||||||
if (IsSelectedItem(mechanic_workingPump.Item))
|
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
|
} while (mechanic_workingPump.FlowPercentage >= 0 || !mechanic_workingPump.IsActive); // Highlight until draining
|
||||||
@@ -348,6 +348,9 @@ namespace Barotrauma.Tutorials
|
|||||||
TriggerTutorialSegment(4); // Deconstruct
|
TriggerTutorialSegment(4); // Deconstruct
|
||||||
|
|
||||||
SetHighlight(mechanic_craftingCabinet.Item, true);
|
SetHighlight(mechanic_craftingCabinet.Item, true);
|
||||||
|
|
||||||
|
bool gotOxygenTank = false;
|
||||||
|
bool gotSodium = false;
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
if (mechanic.SelectedConstruction == mechanic_craftingCabinet.Item)
|
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;
|
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);
|
yield return new WaitForSeconds(1.0f, false);
|
||||||
SetHighlight(mechanic_craftingCabinet.Item, false);
|
SetHighlight(mechanic_craftingCabinet.Item, false);
|
||||||
SetHighlight(mechanic_deconstructor.Item, true);
|
SetHighlight(mechanic_deconstructor.Item, true);
|
||||||
@@ -424,7 +437,9 @@ namespace Barotrauma.Tutorials
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
yield return null;
|
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);
|
SetHighlight(mechanic_deconstructor.Item, false);
|
||||||
RemoveCompletedObjective(segments[4]);
|
RemoveCompletedObjective(segments[4]);
|
||||||
@@ -482,7 +497,7 @@ namespace Barotrauma.Tutorials
|
|||||||
|
|
||||||
// Room 5
|
// Room 5
|
||||||
do { yield return null; } while (!mechanic_fireSensor.MotionDetected);
|
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
|
do { yield return null; } while (!mechanic_fire.Removed); // Wait until extinguished
|
||||||
yield return new WaitForSeconds(3f, false);
|
yield return new WaitForSeconds(3f, false);
|
||||||
RemoveCompletedObjective(segments[6]);
|
RemoveCompletedObjective(segments[6]);
|
||||||
@@ -528,7 +543,7 @@ namespace Barotrauma.Tutorials
|
|||||||
mechanic.RemoveActiveObjectiveEntity(mechanic_brokenWall_2);
|
mechanic.RemoveActiveObjectiveEntity(mechanic_brokenWall_2);
|
||||||
yield return new WaitForSeconds(2f, false);
|
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);
|
SetHighlight(mechanic_brokenPump.Item, true);
|
||||||
mechanic_brokenPump.CanBeSelected = true;
|
mechanic_brokenPump.CanBeSelected = true;
|
||||||
Repairable repairablePumpComponent = mechanic_brokenPump.Item.GetComponent<Repairable>();
|
Repairable repairablePumpComponent = mechanic_brokenPump.Item.GetComponent<Repairable>();
|
||||||
@@ -554,9 +569,9 @@ namespace Barotrauma.Tutorials
|
|||||||
{
|
{
|
||||||
if (IsSelectedItem(mechanic_brokenPump.Item))
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+32
-10
@@ -89,19 +89,34 @@ namespace Barotrauma.Tutorials
|
|||||||
radioSpeakerName = TextManager.Get("Tutorial.Radio.Speaker");
|
radioSpeakerName = TextManager.Get("Tutorial.Radio.Speaker");
|
||||||
officer = Character.Controlled;
|
officer = Character.Controlled;
|
||||||
|
|
||||||
var handcuffs = officer.Inventory.FindItemByIdentifier("handcuffs");
|
var handcuffs = FindOrGiveItem(officer, "handcuffs");
|
||||||
handcuffs.Unequip(officer);
|
handcuffs.Unequip(officer);
|
||||||
officer.Inventory.RemoveItem(handcuffs);
|
officer.Inventory.RemoveItem(handcuffs);
|
||||||
|
|
||||||
var stunbaton = officer.Inventory.FindItemByIdentifier("stunbaton");
|
var stunbaton = FindOrGiveItem(officer, "stunbaton");
|
||||||
stunbaton.Unequip(officer);
|
stunbaton.Unequip(officer);
|
||||||
officer.Inventory.RemoveItem(stunbaton);
|
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);
|
ballistichelmet.Unequip(officer);
|
||||||
officer.Inventory.RemoveItem(ballistichelmet);
|
officer.Inventory.RemoveItem(ballistichelmet);
|
||||||
|
|
||||||
var bodyarmor = officer.Inventory.FindItemByIdentifier("bodyarmor");
|
var bodyarmor = FindOrGiveItem(officer, "bodyarmor");
|
||||||
bodyarmor.Unequip(officer);
|
bodyarmor.Unequip(officer);
|
||||||
officer.Inventory.RemoveItem(bodyarmor);
|
officer.Inventory.RemoveItem(bodyarmor);
|
||||||
|
|
||||||
@@ -109,6 +124,11 @@ namespace Barotrauma.Tutorials
|
|||||||
officer_gunIcon = gunOrder.SymbolSprite;
|
officer_gunIcon = gunOrder.SymbolSprite;
|
||||||
officer_gunIconColor = gunOrder.Color;
|
officer_gunIconColor = gunOrder.Color;
|
||||||
|
|
||||||
|
var bandage = FindOrGiveItem(officer, "antibleeding1");
|
||||||
|
bandage.Unequip(officer);
|
||||||
|
officer.Inventory.RemoveItem(bandage);
|
||||||
|
FindOrGiveItem(officer, "antibleeding1");
|
||||||
|
|
||||||
// Other tutorial items
|
// Other tutorial items
|
||||||
tutorial_mechanicFinalDoorLight = Item.ItemList.Find(i => i.HasTag("tutorial_mechanicfinaldoorlight")).GetComponent<LightComponent>();
|
tutorial_mechanicFinalDoorLight = Item.ItemList.Find(i => i.HasTag("tutorial_mechanicfinaldoorlight")).GetComponent<LightComponent>();
|
||||||
tutorial_submarineSteering = Item.ItemList.Find(i => i.HasTag("command")).GetComponent<Steering>();
|
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;
|
while (GameMain.Instance.LoadingScreenOpen) yield return null;
|
||||||
|
|
||||||
|
yield return new WaitForSeconds(0.01f);
|
||||||
|
|
||||||
// Room 1
|
// Room 1
|
||||||
SoundPlayer.PlayDamageSound("StructureBlunt", 10, Character.Controlled.WorldPosition);
|
SoundPlayer.PlayDamageSound("StructureBlunt", 10, Character.Controlled.WorldPosition);
|
||||||
while (shakeTimer > 0.0f) // Wake up, shake
|
while (shakeTimer > 0.0f) // Wake up, shake
|
||||||
@@ -238,7 +260,7 @@ namespace Barotrauma.Tutorials
|
|||||||
//RemoveCompletedObjective(segments[0]);
|
//RemoveCompletedObjective(segments[0]);
|
||||||
SetHighlight(officer_equipmentCabinet.Item, false);
|
SetHighlight(officer_equipmentCabinet.Item, false);
|
||||||
do { yield return null; } while (IsSelectedItem(officer_equipmentCabinet.Item));
|
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
|
do
|
||||||
{
|
{
|
||||||
if (!officer.HasEquippedItem("stunbaton"))
|
if (!officer.HasEquippedItem("stunbaton"))
|
||||||
@@ -249,12 +271,12 @@ namespace Barotrauma.Tutorials
|
|||||||
{
|
{
|
||||||
HighlightInventorySlot(officer.Inventory, "bodyarmor", highlightColor, .5f, .5f, 0f);
|
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);
|
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]);
|
RemoveCompletedObjective(segments[1]);
|
||||||
SetDoorAccess(officer_firstDoor, officer_firstDoorLight, true);
|
SetDoorAccess(officer_firstDoor, officer_firstDoorLight, true);
|
||||||
|
|
||||||
@@ -291,7 +313,7 @@ namespace Barotrauma.Tutorials
|
|||||||
SetHighlight(officer_ammoShelf_2.Item, false);
|
SetHighlight(officer_ammoShelf_2.Item, false);
|
||||||
RemoveCompletedObjective(segments[3]);
|
RemoveCompletedObjective(segments[3]);
|
||||||
yield return new WaitForSeconds(2f, false);
|
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 = SpawnMonster("hammerhead", officer_hammerheadSpawnPos);
|
||||||
officer_hammerhead.AIController.SelectTarget(officer.AiTarget);
|
officer_hammerhead.AIController.SelectTarget(officer.AiTarget);
|
||||||
SetHighlight(officer_coilgunPeriscope, true);
|
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_rangedWeaponSensor.MotionDetected);
|
||||||
do { yield return null; } while (!officer_thirdDoor.IsOpen);
|
do { yield return null; } while (!officer_thirdDoor.IsOpen);
|
||||||
yield return new WaitForSeconds(3f, false);
|
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);
|
SetHighlight(officer_rangedWeaponHolder.Item, true);
|
||||||
do { yield return null; } while (!officer_rangedWeaponHolder.Inventory.IsEmpty()); // Wait until looted
|
do { yield return null; } while (!officer_rangedWeaponHolder.Inventory.IsEmpty()); // Wait until looted
|
||||||
SetHighlight(officer_rangedWeaponHolder.Item, false);
|
SetHighlight(officer_rangedWeaponHolder.Item, false);
|
||||||
+15
-4
@@ -31,8 +31,8 @@ namespace Barotrauma.Tutorials
|
|||||||
protected Color highlightColor = Color.OrangeRed;
|
protected Color highlightColor = Color.OrangeRed;
|
||||||
protected Color uiHighlightColor = new Color(150, 50, 0);
|
protected Color uiHighlightColor = new Color(150, 50, 0);
|
||||||
protected Color buttonHighlightColor = new Color(255, 100, 0);
|
protected Color buttonHighlightColor = new Color(255, 100, 0);
|
||||||
protected Color inaccessibleColor = Color.Red;
|
protected Color inaccessibleColor = GUI.Style.Red;
|
||||||
protected Color accessibleColor = Color.Green;
|
protected Color accessibleColor = GUI.Style.Green;
|
||||||
|
|
||||||
public ScenarioTutorial(XElement element) : base(element)
|
public ScenarioTutorial(XElement element) : base(element)
|
||||||
{
|
{
|
||||||
@@ -88,7 +88,7 @@ namespace Barotrauma.Tutorials
|
|||||||
GameMain.GameSession.StartRound(levelSeed);
|
GameMain.GameSession.StartRound(levelSeed);
|
||||||
}
|
}
|
||||||
|
|
||||||
GameMain.GameSession.EventManager.Events.Clear();
|
GameMain.GameSession.EventManager.ActiveEvents.Clear();
|
||||||
GameMain.GameSession.EventManager.Enabled = false;
|
GameMain.GameSession.EventManager.Enabled = false;
|
||||||
GameMain.GameScreen.Select();
|
GameMain.GameScreen.Select();
|
||||||
|
|
||||||
@@ -102,7 +102,7 @@ namespace Barotrauma.Tutorials
|
|||||||
Submarine.MainSub.GodMode = true;
|
Submarine.MainSub.GodMode = true;
|
||||||
|
|
||||||
CharacterInfo charInfo = configElement.Element("Character") == null ?
|
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"));
|
new CharacterInfo(configElement.Element("Character"));
|
||||||
|
|
||||||
WayPoint wayPoint = GetSpawnPoint(charInfo);
|
WayPoint wayPoint = GetSpawnPoint(charInfo);
|
||||||
@@ -298,5 +298,16 @@ namespace Barotrauma.Tutorials
|
|||||||
character.Bloodloss = 0.0f;
|
character.Bloodloss = 0.0f;
|
||||||
character.SetStun(0.0f, true);
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+53
-33
@@ -99,9 +99,9 @@ namespace Barotrauma.Tutorials
|
|||||||
public static void Init()
|
public static void Init()
|
||||||
{
|
{
|
||||||
Tutorials = new List<Tutorial>();
|
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;
|
if (doc?.Root == null) continue;
|
||||||
|
|
||||||
foreach (XElement element in doc.Root.Elements())
|
foreach (XElement element in doc.Root.Elements())
|
||||||
@@ -372,9 +372,10 @@ namespace Barotrauma.Tutorials
|
|||||||
|
|
||||||
private void CreateObjectiveGUI(TutorialSegment segment, int index, TutorialContentTypes type)
|
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) =>
|
segment.ReplayButton.OnClicked += (GUIButton btn, object userdata) =>
|
||||||
{
|
{
|
||||||
if (type == TutorialContentTypes.Video)
|
if (type == TutorialContentTypes.Video)
|
||||||
@@ -389,13 +390,15 @@ namespace Barotrauma.Tutorials
|
|||||||
};
|
};
|
||||||
|
|
||||||
string objectiveTitleText = TextManager.ParseInputTypes(objectiveTranslated);
|
string objectiveTitleText = TextManager.ParseInputTypes(objectiveTranslated);
|
||||||
int yOffset = (int)((GUI.ObjectiveNameFont.MeasureString(objectiveTitleText).Y / 2f + 5));
|
int yOffset = (int)((GUI.SubHeadingFont.MeasureString(objectiveTitleText).Y + 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) }*/,
|
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.ObjectiveTitleFont, textAlignment: Alignment.CenterRight);
|
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.LargeFont.MeasureString(objectiveText).X, yOffset), segment.ReplayButton.RectTransform, Anchor.CenterLeft, Pivot.TopLeft) /*{ AbsoluteOffset = new Point((int)(10 * GUI.Scale), 0) }*/,
|
||||||
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.LargeFont, textAlignment: Alignment.CenterLeft);
|
||||||
objectiveText, textColor: new Color(4, 180, 108), font: GUI.ObjectiveNameFont, textAlignment: Alignment.CenterRight);
|
|
||||||
|
|
||||||
segment.LinkedTitle.Color = segment.LinkedTitle.HoverColor = segment.LinkedTitle.PressedColor = segment.LinkedTitle.SelectedColor = Color.Transparent;
|
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;
|
segment.LinkedText.Color = segment.LinkedText.HoverColor = segment.LinkedText.PressedColor = segment.LinkedText.SelectedColor = Color.Transparent;
|
||||||
@@ -449,19 +452,19 @@ namespace Barotrauma.Tutorials
|
|||||||
RectTransform rectTA;
|
RectTransform rectTA;
|
||||||
if (objectiveTextWidth > objectiveTitleWidth)
|
if (objectiveTextWidth > objectiveTitleWidth)
|
||||||
{
|
{
|
||||||
rectTA = new RectTransform(new Point(checkMarkWidth, checkMarkHeight), segment.ReplayButton.RectTransform, Anchor.BottomLeft, Pivot.BottomLeft);
|
rectTA = new RectTransform(new Point(checkMarkWidth, checkMarkHeight), segment.ReplayButton.RectTransform, Anchor.BottomRight, Pivot.BottomRight);
|
||||||
rectTA.AbsoluteOffset = new Point(-rectTA.Rect.Width - (int)(10 * GUI.Scale), 0);
|
rectTA.AbsoluteOffset = new Point(-rectTA.Rect.Width - (int)(25 * GUI.Scale), 0);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
rectTA = new RectTransform(new Point(checkMarkWidth, checkMarkHeight), segment.ReplayButton.RectTransform, Anchor.BottomLeft, Pivot.BottomLeft);
|
rectTA = new RectTransform(new Point(checkMarkWidth, checkMarkHeight), segment.ReplayButton.RectTransform, Anchor.BottomRight, Pivot.BottomRight);
|
||||||
rectTA.AbsoluteOffset = new Point(-rectTA.Rect.Width - (int)(10 * GUI.Scale) - (objectiveTitleWidth - objectiveTextWidth), 0);
|
rectTA.AbsoluteOffset = new Point(-rectTA.Rect.Width - (int)(25 * GUI.Scale) - (objectiveTitleWidth - objectiveTextWidth), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
GUIImage checkmark = new GUIImage(rectTA, "CheckMark");
|
GUIImage checkmark = new GUIImage(rectTA, "CheckMark");
|
||||||
checkmark.Color = checkmark.SelectedColor = checkmark.HoverColor = checkmark.PressedColor = color;
|
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");
|
GUIImage stroke = new GUIImage(rectTB, "Stroke");
|
||||||
stroke.Color = stroke.SelectedColor = stroke.HoverColor = stroke.PressedColor = color;
|
stroke.Color = stroke.SelectedColor = stroke.HoverColor = stroke.PressedColor = color;
|
||||||
|
|
||||||
@@ -493,14 +496,6 @@ namespace Barotrauma.Tutorials
|
|||||||
protected GUIComponent CreateInfoFrame(string title, string text, int width = 300, int height = 80, string anchorStr = "", bool hasButton = false, Action callback = null, Action showVideo = null)
|
protected GUIComponent CreateInfoFrame(string title, string text, int width = 300, int height = 80, string anchorStr = "", bool hasButton = false, Action callback = null, Action showVideo = null)
|
||||||
{
|
{
|
||||||
if (hasButton) height += 60;
|
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;
|
Anchor anchor = Anchor.TopRight;
|
||||||
|
|
||||||
@@ -509,12 +504,23 @@ namespace Barotrauma.Tutorials
|
|||||||
Enum.TryParse(anchorStr, out anchor);
|
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) });
|
string wrappedText = ToolBox.WrapText(text, width, GUI.Font);
|
||||||
infoBlock.Flash(Color.Green);
|
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,
|
Stretch = true,
|
||||||
AbsoluteSpacing = 5
|
AbsoluteSpacing = 5
|
||||||
@@ -523,26 +529,35 @@ namespace Barotrauma.Tutorials
|
|||||||
if (title.Length > 0)
|
if (title.Length > 0)
|
||||||
{
|
{
|
||||||
var titleBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoContent.RectTransform),
|
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;
|
titleBlock.RectTransform.IsFixedSize = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
var textBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoContent.RectTransform), " " + text, wrap: true);
|
List<ColorData> colorData = ColorData.GetColorData(text, out text);
|
||||||
textBlock.RectTransform.IsFixedSize = true;
|
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;
|
infoBoxClosedCallback = callback;
|
||||||
|
|
||||||
if (hasButton)
|
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
|
RelativeSpacing = 0.1f
|
||||||
};
|
};
|
||||||
buttonContainer.RectTransform.IsFixedSize = true;
|
buttonContainer.RectTransform.IsFixedSize = true;
|
||||||
|
|
||||||
if (showVideo != null)
|
if (showVideo != null)
|
||||||
{
|
{
|
||||||
|
buttonContainer.Stretch = true;
|
||||||
var videoButton = new GUIButton(new RectTransform(new Vector2(0.4f, 1.0f), buttonContainer.RectTransform),
|
var videoButton = new GUIButton(new RectTransform(new Vector2(0.4f, 1.0f), buttonContainer.RectTransform),
|
||||||
TextManager.Get("Video"), style: "GUIButtonLarge")
|
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")
|
TextManager.Get("OK"), style: "GUIButtonLarge")
|
||||||
{
|
{
|
||||||
OnClicked = CloseInfoFrame
|
OnClicked = CloseInfoFrame
|
||||||
@@ -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
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -20,7 +20,7 @@ namespace Barotrauma.Items.Components
|
|||||||
|
|
||||||
partial void DischargeProjSpecific()
|
partial void DischargeProjSpecific()
|
||||||
{
|
{
|
||||||
PlaySound(ActionType.OnUse, item.WorldPosition);
|
PlaySound(ActionType.OnUse);
|
||||||
foreach (Node node in nodes)
|
foreach (Node node in nodes)
|
||||||
{
|
{
|
||||||
GameMain.ParticleManager.CreateParticle("swirlysmoke", node.WorldPosition, Vector2.Zero);
|
GameMain.ParticleManager.CreateParticle("swirlysmoke", node.WorldPosition, Vector2.Zero);
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
Reference in New Issue
Block a user