├── SBCameraScroll ├── thumbnail.png ├── AssetBundles │ └── modded_shaders ├── modinfo.json └── workshopdata.json ├── SourceCode ├── TypeCameras │ ├── IAmATypeCamera.cs │ ├── SwitchTypeCamera.cs │ ├── VanillaTypeCamera.cs │ └── PositionTypeCamera.cs ├── GhostWorldPresenceMod.cs ├── GoldFlakesMod.cs ├── OverWorldMod.cs ├── FScreenMod.cs ├── PlayerGraphicsMod.cs ├── ShortcutHandlerMod.cs ├── WorldLoaderMod.cs ├── RainWorldGameMod.cs ├── SplitScreenCoop │ └── SplitScreenCoopMod.cs ├── GlobalUsings.cs ├── WormGrassPatchMod.cs ├── RoomMod.cs ├── SBCameraScroll.csproj ├── SuperStructureProjectorMod.cs ├── ImprovedInput │ ├── RWInputMod.cs │ └── PlayerMod.cs ├── WaterMod.cs ├── LevelTexCombinerMod.cs ├── RippleCameraDataMod.cs ├── AboveCloudsViewMod.cs ├── RainWorldMod.cs ├── MoreSlugcatsMod.cs ├── AbstractRoomMod.cs ├── WormGrassMod.cs ├── MainMod.cs └── Util.cs ├── .gitignore ├── variables.ps1 ├── ModdedShaders ├── _UrbanLife.cginc ├── _DynamicLevelClip.cginc ├── _BrainMoldClip.cginc ├── _ShaderFix.cginc ├── _Snow.cginc ├── _Functions.cginc ├── _TerrainMask.cginc ├── _RippleClip.cginc ├── UnderWaterLight.shader ├── Fog.shader ├── SporesSnow.shader ├── LevelBlend.shader ├── DeepProcessing.shader ├── Decal.shader ├── DisplaySnowShader.shader ├── DeepWater.shader └── LevelHeat.shader ├── linux_build.sh ├── windows_zip_mod.ps1 ├── linux_upload_mod.sh ├── LICENSE-MIT ├── windows_build.ps1 └── SBCameraScroll.sln /SBCameraScroll/thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SchuhBaum/SBCameraScroll/HEAD/SBCameraScroll/thumbnail.png -------------------------------------------------------------------------------- /SBCameraScroll/AssetBundles/modded_shaders: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SchuhBaum/SBCameraScroll/HEAD/SBCameraScroll/AssetBundles/modded_shaders -------------------------------------------------------------------------------- /SourceCode/TypeCameras/IAmATypeCamera.cs: -------------------------------------------------------------------------------- 1 | namespace SBCameraScroll; 2 | 3 | public interface IAmATypeCamera { 4 | public void Reset(); 5 | public void Update(); 6 | } 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .git/ 3 | 4 | ModdedShaders/*_backup.shader 5 | SourceCode/bin 6 | SourceCode/obj 7 | 8 | # SBCameraScroll/AssetBundles/ 9 | SBCameraScroll/plugins/ 10 | 11 | SBCameraScroll.zip 12 | steam_description.txt 13 | 14 | **/*.meta 15 | **/*.manifest 16 | .vscode 17 | 18 | # windows_build.ps1 19 | # windows_zip_mod_files.ps1 20 | tags 21 | -------------------------------------------------------------------------------- /variables.ps1: -------------------------------------------------------------------------------- 1 | 2 | $dir = $PSScriptRoot 3 | $ErrorActionPreference = "Stop" 4 | 5 | $config = "Release" 6 | 7 | $mod_name = "SBCameraScroll" 8 | $dll_name = "$mod_name.dll" 9 | $pdb_name = "$mod_name.pdb" 10 | 11 | $asset_bundles_path = "$dir\$mod_name\assetbundles" 12 | 13 | $downloads_path = "$HOME\downloads" 14 | $zip_path = "$downloads_path\$mod_name.zip" 15 | -------------------------------------------------------------------------------- /ModdedShaders/_UrbanLife.cginc: -------------------------------------------------------------------------------- 1 | #pragma multi_compile _ URBANLIFE 2 | sampler2D _UrbanShadowsBlurGrab; 3 | float2 _UrbanLifeCamPos; 4 | float UrbanLifeShadows(float2 scrPos, float depth){// TODO: some nasty hardcoding here vvv 5 | return step(.3,tex2D(_UrbanShadowsBlurGrab,((scrPos+_UrbanLifeCamPos*float2(0.0037,.0252))*float2(.2,.2))*float2(1,.25)+float2(0,.06+depth*.002))); 6 | } 7 | -------------------------------------------------------------------------------- /SBCameraScroll/modinfo.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "SBCameraScroll", 3 | "name": "SBCameraScroll", 4 | "version": "3.2.1", 5 | "authors": "SchuhBaum", 6 | "description": "Creates a smooth, scrolling camera that moves with the slugcat. Based on pipi toki's CameraScroll mod.INCOMPATIBILITIES:- The zoom camera option in the Slugcat Eyebrow Raise mod.", 7 | "requirements": [], 8 | "requirements_names": [], 9 | "target_game_version": "v1.11", 10 | "tags": [ 11 | "Accessibility", 12 | "Game Mechanics" 13 | ], 14 | "checksum_override_version": true 15 | } 16 | -------------------------------------------------------------------------------- /linux_build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cd "./SourceCode" || exit 4 | 5 | config="Release" 6 | dotnet build -c "$config" 7 | 8 | $mod_name = "SBCameraScroll" 9 | $dll_name = "$mod_name.dll" 10 | $pdb_name = "$mod_name.pdb" 11 | 12 | dest_path="../$mod_name/plugins" 13 | mkdir -p $dest_path 14 | 15 | src_file=$(find "bin/$config" -type f -name "$dll_name") 16 | dest_file="$dest_path/$dll_name" 17 | mv -f "$src_file" "$dest_file" 18 | 19 | src_file=$(find "bin/$config" -type f -name "$pdb_name") 20 | dest_file="$dest_path/$pdb_name" 21 | mv -f "$src_file" "$dest_file" 22 | 23 | cd .. 24 | -------------------------------------------------------------------------------- /ModdedShaders/_DynamicLevelClip.cginc: -------------------------------------------------------------------------------- 1 | #pragma multi_compile _ COMBINEDLEVEL 2 | #pragma multi_compile _ RIPPLE 3 | 4 | inline void dynamicLevelClip (float2 scrPos, float2 textCoord) { 5 | #if COMBINEDLEVEL 6 | #if RIPPLE 7 | if (tex2D(_DynamicLevelElements, scrPos).x==0 && tex2D(_GameplayRippleMask,scrPos).x==0.0) return; 8 | #else 9 | if (tex2D(_DynamicLevelElements, scrPos).x==0) return; 10 | #endif 11 | float3 orig = tex2D(_OrigLevelTex, textCoord).xyz; 12 | float3 level = tex2D(_LevelTex, textCoord).xyz; 13 | if (orig.x!=level.x || orig.y != level.y || orig.z != level.z) discard; 14 | #endif 15 | } 16 | -------------------------------------------------------------------------------- /SourceCode/GhostWorldPresenceMod.cs: -------------------------------------------------------------------------------- 1 | namespace SBCameraScroll; 2 | 3 | internal static class GhostWorldPresenceMod { 4 | // same as in AboveCloudsViewMod 5 | internal static void OnEnable() { 6 | On.GhostWorldPresence.GhostMode_Room_int += GhostWorldPresence_GhostMode; 7 | } 8 | 9 | // ----------------- // 10 | // private functions // 11 | // ----------------- // 12 | 13 | private static float GhostWorldPresence_GhostMode(On.GhostWorldPresence.orig_GhostMode_Room_int orig, GhostWorldPresence ghost_world_presence, Room room, int cam_pos) { 14 | return orig(ghost_world_presence, room, 0); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /SourceCode/GoldFlakesMod.cs: -------------------------------------------------------------------------------- 1 | namespace SBCameraScroll; 2 | 3 | internal static class GoldFlakesMod { 4 | // same idea as in AboveCloudsViewMod 5 | internal static void OnEnable() { 6 | On.GoldFlakes.GoldFlake.PlaceRandomlyInRoom += GoldFlake_PlaceRandomlyInRoom; 7 | } 8 | 9 | // ----------------- // 10 | // private functions // 11 | // ----------------- // 12 | 13 | private static void GoldFlake_PlaceRandomlyInRoom(On.GoldFlakes.GoldFlake.orig_PlaceRandomlyInRoom orig, GoldFlakes.GoldFlake gold_flake) { 14 | if (gold_flake.savedCamPos == -1) { 15 | orig(gold_flake); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /SBCameraScroll/workshopdata.json: -------------------------------------------------------------------------------- 1 | { 2 | "Title": "SBCameraScroll", 3 | "Description": "Creates a smooth, scrolling camera that moves with the slugcat. Based on pipi toki\u0027s CameraScroll mod.\u003CLINE\u003E\u003CLINE\u003EINCOMPATIBILITIES:\u003CLINE\u003E- The zoom camera option in the Slugcat Eyebrow Raise mod.", 4 | "ID": "SBCameraScroll", 5 | "Version": "3.2.1", 6 | "TargetGameVersion": "v1.11", 7 | "Requirements": "", 8 | "RequirementNames": "", 9 | "Authors": "SchuhBaum", 10 | "Visibility": "Public", 11 | "Tags": [ 12 | "Game Mechanics", 13 | "Accessibility" 14 | ], 15 | "WorkshopID": 2928752589, 16 | "UploadFilesOnly": true, 17 | "UploadThumbnail": false 18 | } 19 | -------------------------------------------------------------------------------- /SourceCode/OverWorldMod.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace SBCameraScroll; 3 | 4 | internal static class OverWorldMod { 5 | internal static void OnEnable() { 6 | On.OverWorld.WorldLoaded += OverWorld_WorldLoaded; 7 | } 8 | 9 | // 10 | // private 11 | // 12 | 13 | private static void OverWorld_WorldLoaded(On.OverWorld.orig_WorldLoaded orig, OverWorld over_world, bool warp_used) { 14 | orig(over_world, warp_used); 15 | foreach (AbstractRoom abstract_room in _all_abstract_room_fields.Keys) { 16 | if (over_world.activeWorld.IsRoomInRegion(abstract_room.index)) continue; 17 | DestroyWormGrassInAbstractRoom(abstract_room); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /SourceCode/FScreenMod.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace SBCameraScroll; 3 | 4 | internal static class FScreenMod { 5 | internal static void OnEnable() { 6 | On.FScreen.ctor += FScreen_Ctor; 7 | } 8 | 9 | // 10 | // private 11 | // 12 | 13 | private static void FScreen_Ctor(On.FScreen.orig_ctor orig, FScreen f_screen, FutileParams futile_params) { 14 | orig(f_screen, futile_params); 15 | int screen_size_y = Mathf.RoundToInt(Custom.rainWorld.options.ScreenSize.y); 16 | if (screen_size_y == 768) return; 17 | 18 | f_screen.pixelHeight = screen_size_y; 19 | f_screen.UpdateScreenOffset(); 20 | f_screen.renderTexture = new RenderTexture(f_screen.pixelWidth * f_screen.renderScale, f_screen.pixelHeight * f_screen.renderScale, 0); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /SourceCode/PlayerGraphicsMod.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace SBCameraScroll; 3 | 4 | public static class PlayerGraphicsMod { 5 | internal static void OnEnable() { 6 | On.PlayerGraphics.RippleTrailUpdate -= PlayerGraphics_RippleTrailUpdate; 7 | On.PlayerGraphics.RippleTrailUpdate += PlayerGraphics_RippleTrailUpdate; 8 | } 9 | 10 | // 11 | // private 12 | // 13 | 14 | private static void PlayerGraphics_RippleTrailUpdate(On.PlayerGraphics.orig_RippleTrailUpdate orig, PlayerGraphics player_graphics) { 15 | orig(player_graphics); 16 | if (!Option_RippleTrailEffect && player_graphics.rippleTrail != null && !player_graphics.rippleTrail.beingDeleted) 17 | { 18 | player_graphics.rippleTrail.SetProperty(0, 0f); 19 | player_graphics.isRippleTrailDisabled = true; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /windows_zip_mod.ps1: -------------------------------------------------------------------------------- 1 | 2 | . ".\variables.ps1"; 3 | 4 | # 5 | 6 | Write-Host "Running build.ps1..." 7 | & "$dir\windows_build.ps1" 8 | if ($LASTEXITCODE -ne 0) { 9 | Write-Error "Build failed. Aborting." 10 | exit 1 11 | } 12 | 13 | # 14 | # 15 | 16 | Push-Location $dir 17 | 18 | $item_paths = @( 19 | "$mod_name\previously_active_mods.json", 20 | "$mod_name\plugins\profiler.dll", 21 | "$mod_name\plugins\profiler.pdb" 22 | ) 23 | 24 | foreach ($item_path in $item_paths) { 25 | if (Test-Path $item_path) { 26 | Remove-Item -Path "$item_path" -Force 27 | } 28 | } 29 | 30 | 7z a $zip_path "$mod_name\AssetBundles\modded_shaders" "$mod_name\plugins\$dll_name" "$mod_name\plugins\$pdb_name" "$mod_name\modinfo.json" "$mod_name\thumbnail.png" "$mod_name\workshopdata.json" 31 | 32 | Pop-Location 33 | 34 | Write-Host "Archive created at: $zip_path" 35 | 36 | -------------------------------------------------------------------------------- /ModdedShaders/_BrainMoldClip.cginc: -------------------------------------------------------------------------------- 1 | #pragma multi_compile _ RoomHasBrainMold 2 | sampler2D _BrainMold; 3 | sampler2D _PreBrainMold; 4 | 5 | inline float _BrainMoldMask( float2 scrPos ) 6 | { 7 | float brainMoldMask = 0; 8 | #if RoomHasBrainMold 9 | brainMoldMask = tex2D(_PreBrainMold,scrPos)!=tex2D(_BrainMold,scrPos); 10 | #endif 11 | return brainMoldMask; 12 | } 13 | 14 | inline fixed4 _BrainMoldTexture( float2 scrPos ) 15 | { 16 | fixed4 brainMold = 0; 17 | #if RoomHasBrainMold 18 | fixed4 preBrainMold =tex2D(_PreBrainMold,scrPos); 19 | brainMold = tex2D(_BrainMold,scrPos); 20 | brainMold.a = (preBrainMold!=brainMold); 21 | #endif 22 | return brainMold; 23 | } 24 | 25 | inline void _BrainMoldClip( float2 scrPos ) 26 | { 27 | #if RoomHasBrainMold 28 | bool brainMold = _BrainMoldMask( scrPos )>0; 29 | if (brainMold) discard; 30 | #endif 31 | } 32 | 33 | -------------------------------------------------------------------------------- /linux_upload_mod.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This uses my rain_world_uploader for Linux. 4 | # Link: https://github.com/SchuhBaum/rain_world_uploader/ 5 | 6 | prev_wd="$(pwd)" 7 | cur_wd_relative="$(dirname "${BASH_SOURCE[0]}")" 8 | cur_wd="$(cd $cur_wd_relative && pwd)" 9 | 10 | ./build_linux.sh 11 | zip -r SBCameraScroll.zip ./SBCameraScroll/ 12 | rm $HOME/downloads/SBCameraScroll.zip 13 | mv ./SBCameraScroll.zip $HOME/downloads/SBCameraScroll.zip 14 | 15 | cd "$prev_wd" 16 | 17 | 18 | read -p "Upload mod? (yes/NO) $ " ready 19 | 20 | if ! [ "$ready" == "yes" ]; then 21 | echo "Exiting." 22 | exit 0 23 | fi 24 | 25 | mod_id="2928752589" 26 | mod_name="SBCameraScroll" 27 | 28 | prev_wd="$(pwd)" 29 | cur_wd_relative="$(dirname "${BASH_SOURCE[0]}")" 30 | cur_wd="$(cd $cur_wd_relative && pwd)" 31 | 32 | $HOME/rain_world_uploader/rain_world_uploader.out "$mod_id" "./$mod_name" 33 | 34 | cd "$prev_wd" 35 | -------------------------------------------------------------------------------- /ModdedShaders/_ShaderFix.cginc: -------------------------------------------------------------------------------- 1 | // (https://github.com/PJB3005/RainWorldMods/blob/master/Sharpener/_ShaderFix.cginc) 2 | // 3 | // When rendering to a RenderTexture, Unity tries to flip the projection matrix so everything renders upside down. 4 | // This is really bad, because none of the shaders in rain world are coded to handle this. 5 | // So, we tell Unity to get lost and override ComputeScreenPos() so that it ignores the "flip the Y dir" thing. 6 | // To go along with us changing the projection matrix. 7 | 8 | //#define ComputeScreenPos(p) _ComputeScreenPos(p) 9 | 10 | //inline float4 _ComputeScreenPos (float4 pos) { 11 | // float4 o = pos * 0.5f; 12 | // #if defined(UNITY_HALF_TEXEL_OFFSET) 13 | // o.xy += o.w * _ScreenParams.zw; 14 | // #else 15 | // o.xy += o.w; 16 | // #endif 17 | // 18 | // #if defined(SHADER_API_FLASH) 19 | // o.xy *= unity_NPOTScale.xy; 20 | // #endif 21 | // 22 | // o.zw = pos.zw; 23 | // return o; 24 | //} -------------------------------------------------------------------------------- /SourceCode/ShortcutHandlerMod.cs: -------------------------------------------------------------------------------- 1 | namespace SBCameraScroll; 2 | 3 | public static class ShortcutHandlerMod { 4 | // ---------------- // 5 | // public functions // 6 | // ---------------- // 7 | 8 | public static ShortcutHandler.ShortCutVessel? GetShortcutVessel(ShortcutHandler? shortcut_handler, AbstractCreature? abstract_creature) { 9 | if (shortcut_handler == null || abstract_creature == null || abstract_creature.realizedCreature?.inShortcut == false) { 10 | return null; 11 | } 12 | 13 | 14 | foreach (AbstractPhysicalObject abstract_physical_object in abstract_creature.GetAllConnectedObjects()) // needed when carried by other creatures 15 | { 16 | if (abstract_physical_object.realizedObject is Creature creature) { 17 | foreach (ShortcutHandler.ShortCutVessel vessel in shortcut_handler.transportVessels) { 18 | if (vessel.creature == creature) { 19 | return vessel; 20 | } 21 | } 22 | } 23 | } 24 | return null; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 SchuhBaum 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /SourceCode/WorldLoaderMod.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace SBCameraScroll; 3 | 4 | internal static class WorldLoaderMod { 5 | internal static void OnEnable() { 6 | // CRS has a `REPLACEROOM` feature; I need to get the changed room name in order 7 | // to merge the textures; CRS tracks this information too; but so far I only found 8 | // it inside an internal class; 9 | // 10 | // This is part of vanilla now. But the alt name is set after the room 11 | // is created. Update the attached fields anyways. 12 | IL.WorldLoader.LoadAbstractRoom += WorldLoader_LoadAbstractRoom; 13 | } 14 | 15 | // 16 | // public 17 | // 18 | 19 | public static void WorldLoaderMod_CheckForAltRoomName(AbstractRoom abstract_room) { 20 | if (abstract_room.altFileName != null) { 21 | UpdateAttachedFields(abstract_room); 22 | } 23 | } 24 | 25 | // 26 | // private 27 | // 28 | 29 | private static void WorldLoader_LoadAbstractRoom(ILContext context) { 30 | ILCursor cursor = new ILCursor(context); 31 | cursor.Emit(OpCodes.Ldarg_2); 32 | cursor.EmitDelegate(WorldLoaderMod_CheckForAltRoomName); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /SourceCode/RainWorldGameMod.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace SBCameraScroll; 3 | 4 | internal static class RainWorldGameMod { 5 | internal static void OnEnable() { 6 | On.RainWorldGame.ctor += RainWorldGame_Ctor; 7 | On.RainWorldGame.ShutDownProcess += RainWorldGame_ShutDownProcess; 8 | } 9 | 10 | // 11 | // private 12 | // 13 | 14 | private static void RainWorldGame_Ctor(On.RainWorldGame.orig_ctor orig, RainWorldGame game, ProcessManager manager) { 15 | AbstractRoomMod._all_abstract_room_fields.Clear(); 16 | RoomCameraMod._all_room_camera_fields.Clear(); 17 | WormGrassMod._all_worm_grass_fields.Clear(); 18 | 19 | Debug.Log($"{mod_id}: Initialize variables."); 20 | orig(game, manager); 21 | } 22 | 23 | private static void RainWorldGame_ShutDownProcess(On.RainWorldGame.orig_ShutDownProcess orig, RainWorldGame game) { 24 | Debug.Log($"{mod_id}: Cleanup."); 25 | orig(game); 26 | 27 | // Profiler.Api.WriteLine = Debug.Log; 28 | // Profiler.Api.PrintAndReset(); 29 | 30 | AbstractRoomMod._all_abstract_room_fields.Clear(); 31 | RoomCameraMod._all_room_camera_fields.Clear(); 32 | WormGrassMod._all_worm_grass_fields.Clear(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /SourceCode/SplitScreenCoop/SplitScreenCoopMod.cs: -------------------------------------------------------------------------------- 1 | using static SplitScreenCoop.SplitScreenCoop; 2 | using static SplitScreenCoop.SplitScreenCoop.SplitMode; 3 | 4 | namespace SBCameraScroll; 5 | 6 | // use only if is_split_screen_coop_enabled is true; 7 | public static class SplitScreenCoopMod { 8 | // 9 | // variables 10 | // 11 | 12 | public static bool Is_Split => CurrentSplitMode != NoSplit; 13 | public static bool Is_Split_4Screen => CurrentSplitMode == Split4Screen; 14 | public static bool Is_Split_Horizontally => CurrentSplitMode == SplitHorizontal; 15 | public static bool Is_Split_Vertically => CurrentSplitMode == SplitVertical; 16 | 17 | // 18 | // public 19 | // 20 | 21 | public static Vector2 Get_Screen_Offset(RoomCamera room_camera, in Vector2 screen_size) { 22 | if (Is_Split_Horizontally) return new(0.0f, 0.25f * screen_size.y); 23 | if (Is_Split_Vertically) return new(0.25f * screen_size.x, 0.0f); 24 | 25 | if (Is_Split_4Screen) { 26 | if (cameraZoomed[room_camera.cameraNumber]) return new(); 27 | return new(0.25f * screen_size.x, 0.25f * screen_size.y); 28 | } 29 | return new(); 30 | } 31 | 32 | public static bool Is_4Screen_Zoomed_Out(RoomCamera room_camera) => cameraZoomed[room_camera.cameraNumber]; 33 | } 34 | -------------------------------------------------------------------------------- /SourceCode/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | 2 | global using BepInEx; 3 | global using Expedition; 4 | global using Menu; 5 | global using Menu.Remix.MixedUI; 6 | global using Mono.Cecil.Cil; 7 | global using MonoMod.Cil; 8 | global using MonoMod.RuntimeDetour; 9 | global using RWCustom; 10 | global using System; 11 | global using System.Collections.Generic; 12 | global using System.IO; 13 | global using System.Reflection; 14 | global using System.Security.Permissions; 15 | global using System.Threading; 16 | global using Unity.Collections; 17 | global using UnityEngine; 18 | 19 | global using static RWCustom.Custom; 20 | global using static SBCameraScroll.AbstractRoomMod; 21 | global using static SBCameraScroll.MainMod; 22 | global using static SBCameraScroll.MainModOptions; 23 | global using static SBCameraScroll.PlayerMod; 24 | global using static SBCameraScroll.PositionTypeCamera; 25 | global using static SBCameraScroll.RainWorldMod; 26 | global using static SBCameraScroll.RoomMod; 27 | global using static SBCameraScroll.RoomCameraMod; 28 | global using static SBCameraScroll.ShortcutHandlerMod; 29 | global using static SBCameraScroll.SplitScreenCoopMod; 30 | global using static SBCameraScroll.Util; 31 | global using static SBCameraScroll.VanillaTypeCamera; 32 | global using static SBCameraScroll.WormGrassMod; 33 | global using static WorldLoader.LoadingContext; 34 | -------------------------------------------------------------------------------- /SourceCode/WormGrassPatchMod.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace SBCameraScroll; 3 | 4 | internal static class WormGrassPatchMod { 5 | internal static void OnEnable() { 6 | On.WormGrass.WormGrassPatch.SortTiles += WormGrassPatch_SortTiles; // initializes variables // in ctor tiles.Count is not up to date 7 | } 8 | 9 | // 10 | // private 11 | // 12 | 13 | private static void WormGrassPatch_SortTiles(On.WormGrass.WormGrassPatch.orig_SortTiles orig, WormGrass.WormGrassPatch worm_grass_patch) { 14 | // some smaller worms will always be visible // they are added directly to rooms 15 | // for larger worms: just their information is stored and they are created/destroyed later 16 | orig(worm_grass_patch); 17 | 18 | // setting up cosmeticWormsOnTile 19 | var worm_grass_fields = worm_grass_patch.wormGrass.GetFields(); 20 | int tile_count = worm_grass_patch.tiles.Count; 21 | 22 | if (!worm_grass_fields.cosmetic_worms_on_tiles.ContainsKey(worm_grass_patch)) { 23 | worm_grass_fields.cosmetic_worms_on_tiles.Add(worm_grass_patch, new List[tile_count]); 24 | } 25 | 26 | for (int tile_index = 0; tile_index < tile_count; ++tile_index) { 27 | worm_grass_fields.cosmetic_worms_on_tiles[worm_grass_patch][tile_index] = new List(); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /windows_build.ps1: -------------------------------------------------------------------------------- 1 | 2 | . ".\variables.ps1"; 3 | 4 | # 5 | 6 | dotnet build "$dir\sourcecode" -c $config -v:detailed 7 | if ($LASTEXITCODE) { 8 | exit $LASTEXITCODE 9 | } 10 | 11 | function copy_file($file_name) { 12 | $src = @(Get-ChildItem -Path "$dir\sourcecode\bin\$config" -Recurse -Filter $file_name | Select-Object -ExpandProperty FullName)[0] 13 | if (-not ($src -eq $null)) { 14 | $dst = Join-Path -Path "$dir\$mod_name\plugins" -ChildPath $file_name 15 | 16 | # Write-Host $src 17 | # Write-Host $dst 18 | 19 | New-Item -ItemType Directory -Path (Split-Path $dst) -Force | Out-Null 20 | Move-Item -Path $src -Destination $dst -Force 21 | } 22 | } 23 | 24 | copy_file($dll_name) 25 | copy_file($pdb_name) 26 | 27 | copy_file("profiler.dll") 28 | copy_file("profiler.pdb") 29 | 30 | $delete_file_paths = @( 31 | "$asset_bundles_path\assetbundles", 32 | "$asset_bundles_path\assetbundles.meta", 33 | "$asset_bundles_path\assetbundles.manifest", 34 | "$asset_bundles_path\assetbundles.manifest.meta", 35 | "$asset_bundles_path\modded_shaders.meta", 36 | "$asset_bundles_path\modded_shaders.manifest", 37 | "$asset_bundles_path\modded_shaders.manifest.meta" 38 | ) 39 | 40 | foreach ($file_path in $delete_file_paths) { 41 | if (Test-Path $file_path) { 42 | Remove-Item -Path "$file_path" -Force 43 | } 44 | } 45 | 46 | -------------------------------------------------------------------------------- /SourceCode/RoomMod.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace SBCameraScroll; 3 | 4 | public static class RoomMod { 5 | internal static void OnEnable() { 6 | // removes DeathFallFocus objects (which create fall focal points); 7 | On.Room.Loaded += Room_Loaded; 8 | } 9 | 10 | // 11 | // public 12 | // 13 | 14 | public static int CameraViewingPoint(Room room, Vector2 position) { 15 | // the original function Room.CameraViewingPoint() does not check the whole texture 16 | // (1400x800); it only checks what you can see of it (1366x768); 17 | // loop backwards to match how camera textures are merged, i.e. later ones can 18 | // override parts of earlier ones; 19 | for (int camera_index = room.cameraPositions.Length - 1; camera_index >= 0; --camera_index) { 20 | Vector2 camera_position = room.cameraPositions[camera_index]; 21 | if (position.x < camera_position.x) continue; 22 | if (position.x > camera_position.x + 1400f) continue; 23 | if (position.y < camera_position.y) continue; 24 | if (position.y > camera_position.y + 800f) continue; 25 | return camera_index; 26 | } 27 | return -1; 28 | } 29 | 30 | // 31 | // private 32 | // 33 | 34 | private static void Room_Loaded(On.Room.orig_Loaded orig, Room room) { 35 | orig(room); 36 | 37 | // these focal points change the height of death fall indicators; 38 | // even when I use the camera height to create a full screen effect 39 | // that moves with the camera, they will pop in and out when in or 40 | // out of range; 41 | // => remove for now; 42 | room.deathFallFocalPoints = new(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /ModdedShaders/_Snow.cginc: -------------------------------------------------------------------------------- 1 | #pragma multi_compile _ SNOW_ON 2 | sampler2D _SnowTex; 3 | float2 _SlowFollowCreatureScreenPos; 4 | #ifndef RIPPLECLIP 5 | #include "_RippleClip.cginc" 6 | #endif 7 | #ifndef COMMONFUNCTIONS 8 | #include "_Functions.cginc" 9 | #endif 10 | 11 | 12 | float2 _scale_from(float2 uv, float2 p, float scale) { 13 | float2 offsetUV = uv - p; 14 | offsetUV *= scale; 15 | return offsetUV + p - uv; 16 | } 17 | 18 | float2 rippleDistortion(fixed rippleMask, float2 scrPos){ 19 | 20 | #if RIPPLE 21 | fixed gameplayShiftTex = rippleMask; 22 | return _scale_from(scrPos,_SlowFollowCreatureScreenPos,1-(smoothstep(.2,-.0,abs(gameplayShiftTex-.25))*.5-saturate(gameplayShiftTex-.2))*.5); 23 | #endif 24 | return 0; 25 | } 26 | 27 | inline fixed4 AddSnow ( fixed4 levelTex, float2 texCoord, float2 scrPos ) { 28 | #if RIPPLE 29 | fixed rippleMask = tex2D(_GameplayRippleMask,scrPos).x; 30 | if (rippleMask > .4) return levelTex; 31 | texCoord+=rippleDistortion(rippleMask, scrPos); 32 | #endif 33 | #if SNOW_ON 34 | fixed4 snowcol = tex2D(_SnowTex, texCoord); 35 | return lerp(levelTex,half4(snowcol.x,0,1,1),snowcol.y); 36 | #else 37 | return levelTex; 38 | #endif 39 | } 40 | 41 | float _snowpulse(float value, float p, float a){ 42 | return smoothstep(a,0,abs(value-p)); 43 | } 44 | 45 | inline fixed Sparkles(fixed depth, float2 spriteUV, float2 screenUV, float4 texCol, sampler2D _UniNoise, float _RAIN) 46 | { 47 | depth = 1-depth; 48 | fixed noise = tex2D(_UniNoise,screenUV*3+depth*5-abs(fmod(spriteUV*1,1)*2-1)*(.002+depth*.002)).x; 49 | fixed noise2 = tex2D(_UniNoise,screenUV*2+depth*3+_RAIN*.001+abs(fmod(spriteUV*3,1)*2-1)*(.004+depth*.004)).y; 50 | 51 | noise = fmod(noise+noise2*2,.1)*10; 52 | return _snowpulse(noise,.1,.01)*(texCol.x!=0); 53 | } 54 | -------------------------------------------------------------------------------- /SourceCode/SBCameraScroll.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net48 5 | enable 6 | 10.0 7 | 3.2.1 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /SBCameraScroll.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.31903.59 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SourceCode", "SourceCode", "{1CE96ABC-977F-9E98-3EDA-16B046FFEBCD}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SBCameraScroll", "SourceCode\SBCameraScroll.csproj", "{B4C76AB6-0A27-4724-AD0F-2CCA20DE9B5B}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Debug|x64 = Debug|x64 14 | Debug|x86 = Debug|x86 15 | Release|Any CPU = Release|Any CPU 16 | Release|x64 = Release|x64 17 | Release|x86 = Release|x86 18 | EndGlobalSection 19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 20 | {B4C76AB6-0A27-4724-AD0F-2CCA20DE9B5B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {B4C76AB6-0A27-4724-AD0F-2CCA20DE9B5B}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {B4C76AB6-0A27-4724-AD0F-2CCA20DE9B5B}.Debug|x64.ActiveCfg = Debug|Any CPU 23 | {B4C76AB6-0A27-4724-AD0F-2CCA20DE9B5B}.Debug|x64.Build.0 = Debug|Any CPU 24 | {B4C76AB6-0A27-4724-AD0F-2CCA20DE9B5B}.Debug|x86.ActiveCfg = Debug|Any CPU 25 | {B4C76AB6-0A27-4724-AD0F-2CCA20DE9B5B}.Debug|x86.Build.0 = Debug|Any CPU 26 | {B4C76AB6-0A27-4724-AD0F-2CCA20DE9B5B}.Release|Any CPU.ActiveCfg = Release|Any CPU 27 | {B4C76AB6-0A27-4724-AD0F-2CCA20DE9B5B}.Release|Any CPU.Build.0 = Release|Any CPU 28 | {B4C76AB6-0A27-4724-AD0F-2CCA20DE9B5B}.Release|x64.ActiveCfg = Release|Any CPU 29 | {B4C76AB6-0A27-4724-AD0F-2CCA20DE9B5B}.Release|x64.Build.0 = Release|Any CPU 30 | {B4C76AB6-0A27-4724-AD0F-2CCA20DE9B5B}.Release|x86.ActiveCfg = Release|Any CPU 31 | {B4C76AB6-0A27-4724-AD0F-2CCA20DE9B5B}.Release|x86.Build.0 = Release|Any CPU 32 | EndGlobalSection 33 | GlobalSection(SolutionProperties) = preSolution 34 | HideSolutionNode = FALSE 35 | EndGlobalSection 36 | GlobalSection(NestedProjects) = preSolution 37 | {B4C76AB6-0A27-4724-AD0F-2CCA20DE9B5B} = {1CE96ABC-977F-9E98-3EDA-16B046FFEBCD} 38 | EndGlobalSection 39 | EndGlobal 40 | -------------------------------------------------------------------------------- /SourceCode/SuperStructureProjectorMod.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace SBCameraScroll; 3 | 4 | internal static class SuperStructureProjectorMod { 5 | // same as in AboveCloudsViewMod 6 | internal static void OnEnable() { 7 | On.SuperStructureProjector.GlyphMatrix.DrawSprites += GlyphMatrix_DrawSprites; 8 | On.SuperStructureProjector.SingleGlyph.DrawSprites += SingleGlyph_DrawSprites; 9 | } 10 | 11 | // ----------------- // 12 | // private functions // 13 | // ----------------- // 14 | 15 | private static void GlyphMatrix_DrawSprites(On.SuperStructureProjector.GlyphMatrix.orig_DrawSprites orig, SuperStructureProjector.GlyphMatrix glyph_matrix, RoomCamera.SpriteLeaser sprite_leaser, RoomCamera room_camera, float time_stacker, Vector2 cam_pos) { 16 | if (room_camera.room == null) { 17 | orig(glyph_matrix, sprite_leaser, room_camera, time_stacker, cam_pos); 18 | return; 19 | } 20 | 21 | Vector2 camera_position = room_camera.room.cameraPositions[room_camera.currentCameraPosition]; 22 | room_camera.room.cameraPositions[room_camera.currentCameraPosition] = room_camera.room.cameraPositions[0]; 23 | orig(glyph_matrix, sprite_leaser, room_camera, time_stacker, cam_pos); 24 | room_camera.room.cameraPositions[room_camera.currentCameraPosition] = camera_position; 25 | } 26 | 27 | private static void SingleGlyph_DrawSprites(On.SuperStructureProjector.SingleGlyph.orig_DrawSprites orig, SuperStructureProjector.SingleGlyph single_glyph, RoomCamera.SpriteLeaser sprite_leaser, RoomCamera room_camera, float time_stacker, Vector2 cam_pos) { 28 | if (room_camera.room == null) { 29 | orig(single_glyph, sprite_leaser, room_camera, time_stacker, cam_pos); 30 | return; 31 | } 32 | 33 | Vector2 camera_position = room_camera.room.cameraPositions[room_camera.currentCameraPosition]; 34 | room_camera.room.cameraPositions[room_camera.currentCameraPosition] = room_camera.room.cameraPositions[0]; 35 | orig(single_glyph, sprite_leaser, room_camera, time_stacker, cam_pos); 36 | room_camera.room.cameraPositions[room_camera.currentCameraPosition] = camera_position; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /SourceCode/TypeCameras/SwitchTypeCamera.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace SBCameraScroll; 3 | 4 | public class SwitchTypeCamera : IAmATypeCamera { 5 | // 6 | // parameters 7 | // 8 | 9 | private readonly RoomCamera _room_camera; 10 | private readonly PositionTypeCamera _position_type_camera; 11 | private readonly VanillaTypeCamera _vanilla_type_camera; 12 | 13 | // 14 | // variables 15 | // 16 | 17 | public bool is_position_type_camera_active = true; 18 | 19 | // 20 | // main 21 | // 22 | 23 | public SwitchTypeCamera(RoomCamera room_camera, RoomCameraFields room_camera_fields) { 24 | this._room_camera = room_camera; 25 | _position_type_camera = new(room_camera, room_camera_fields); 26 | _vanilla_type_camera = new(room_camera, room_camera_fields); 27 | } 28 | 29 | // 30 | // public 31 | // 32 | 33 | public bool Is_Map_Pressed(Player player) { 34 | if (!is_improved_input_enabled) return player.input[0].mp && !player.input[1].mp; 35 | return player.Wants_To_Switch_Camera(); 36 | } 37 | 38 | public void Reset() { 39 | if (is_position_type_camera_active) { 40 | _position_type_camera.Reset(); 41 | return; 42 | } 43 | _vanilla_type_camera.Reset(); 44 | } 45 | 46 | public void Update() { 47 | if (_room_camera.followAbstractCreature == null) return; 48 | if (_room_camera.followAbstractCreature.realizedCreature is not Player player) { 49 | _position_type_camera.Update(); 50 | return; 51 | } 52 | 53 | if (Is_Map_Pressed(player)) { 54 | // this might be helpful since using the vanilla_type_camera might 55 | // register the button press again otherwise; 56 | _position_type_camera.Update(); 57 | is_position_type_camera_active = !is_position_type_camera_active; 58 | 59 | // start a smooth transition next frame 60 | // if vanilla_type_camera is active; 61 | _vanilla_type_camera.follow_abstract_creature_id = null; 62 | return; 63 | } 64 | 65 | if (is_position_type_camera_active) { 66 | _position_type_camera.Update(); 67 | return; 68 | } 69 | _vanilla_type_camera.Update(); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /SourceCode/ImprovedInput/RWInputMod.cs: -------------------------------------------------------------------------------- 1 | 2 | using ImprovedInput; 3 | 4 | namespace SBCameraScroll; 5 | 6 | public static class RWInputMod { 7 | // 8 | // parameters 9 | // 10 | 11 | public static PlayerKeybind center_keybinding = null!; 12 | public static PlayerKeybind switch_keybinding = null!; 13 | 14 | // 15 | // main 16 | // 17 | 18 | public static void Initialize_Custom_Keybindings() { 19 | if (center_keybinding != null) return; 20 | 21 | // initialize after ImprovedInput has; 22 | center_keybinding = PlayerKeybind.Register("SBCameraScroll-Center_Vanilla_Type_Camera", "SBCameraScroll", "Center", KeyCode.None, KeyCode.None); 23 | center_keybinding.HideConflict = other_keybinding => center_keybinding.Can_Hide_Conflict_With(other_keybinding); 24 | switch_keybinding = PlayerKeybind.Register("SBCameraScroll-Switch_Type_Camera", "SBCameraScroll", "Switch", KeyCode.None, KeyCode.None); 25 | switch_keybinding.HideConflict = other_keybinding => switch_keybinding.Can_Hide_Conflict_With(other_keybinding); 26 | } 27 | 28 | // 29 | // public 30 | // 31 | 32 | public static bool Can_Hide_Conflict_With(this PlayerKeybind keybinding, PlayerKeybind other_keybinding) { 33 | for (int player_index_a = 0; player_index_a < maximum_number_of_players; ++player_index_a) { 34 | for (int player_index_b = player_index_a; player_index_b < maximum_number_of_players; ++player_index_b) { 35 | if (!keybinding.ConflictsWith(player_index_a, other_keybinding, player_index_b)) continue; 36 | if (player_index_a != player_index_b) return false; 37 | 38 | // this is the same as having being Unbound() for the current 39 | // custom keybindings; 40 | if (other_keybinding == PlayerKeybind.Map) continue; 41 | 42 | if (other_keybinding == center_keybinding) continue; 43 | if (other_keybinding == switch_keybinding) continue; 44 | return false; 45 | } 46 | } 47 | return true; 48 | } 49 | 50 | public static InputPackageMod Get_Input(Player player) { 51 | InputPackageMod custom_input = new(); 52 | int player_number = player.playerState.playerNumber; 53 | 54 | if (center_keybinding.Unbound(player_number)) { 55 | custom_input.center_camera = player.input[0].mp; 56 | } else { 57 | custom_input.center_camera = center_keybinding.CheckRawPressed(player_number); 58 | } 59 | 60 | if (switch_keybinding.Unbound(player_number)) { 61 | custom_input.switch_camera = player.input[0].mp; 62 | } else { 63 | custom_input.switch_camera = switch_keybinding.CheckRawPressed(player_number); 64 | } 65 | return custom_input; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /SourceCode/WaterMod.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace SBCameraScroll; 3 | 4 | internal static class WaterMod { 5 | internal static void OnEnable() { 6 | IL.Water.DrawSprites += IL_Water_DrawSprites; 7 | } 8 | 9 | // 10 | // private 11 | // 12 | 13 | private static void IL_Water_DrawSprites(ILContext context) { 14 | // LogAllInstructions(context); 15 | ILCursor cursor = new(context); 16 | 17 | if (cursor.TryGotoNext(instr => instr.MatchLdcR4(-10))) { 18 | // BUG: 19 | // In vanilla the lower edge of the deep water mesh does not scroll 20 | // away from the camera in y in most cases. This can lead to a bug 21 | // when zoomed out where the lower edge overtakes the upper edge and 22 | // flips the sprite / water overlay. 23 | 24 | if (can_log_il_hooks) { 25 | Debug.Log($"{mod_id}: IL_Water_DrawSprites: Index {cursor.Index}"); 26 | } 27 | cursor.Goto(cursor.Index + 1); 28 | 29 | // Pop might be a better choice compared to RemoveRange() because it 30 | // leaves label targets intact. 31 | // cursor.Emit(OpCodes.Pop); // pop vanilla -10f 32 | cursor.Emit(OpCodes.Ldarg, 2); 33 | cursor.Emit(OpCodes.Ldarg, 4); 34 | 35 | cursor.EmitDelegate>( 36 | (y_local, room_camera, camera_pos) => { 37 | if (room_camera.room is not Room room || room_camera.IsRoomBlacklisted(room.abstractRoom)) return y_local; 38 | return y_local + room.abstractRoom.GetFields().min_camera_position.y - camera_pos.y; // modded 39 | }); 40 | } else { 41 | if (can_log_il_hooks) { 42 | Debug.Log($"{mod_id}: IL_Water_DrawSprites failed."); 43 | } 44 | return; 45 | } 46 | 47 | if (cursor.TryGotoNext(instr => instr.MatchLdcR4(22), 48 | instr => instr.MatchMul())) { 49 | // This is the same as before but for upside-down water. 50 | 51 | if (can_log_il_hooks) { 52 | Debug.Log($"{mod_id}: IL_Water_DrawSprites: Index {cursor.Index}"); 53 | } 54 | 55 | cursor.Goto(cursor.Index + 2); 56 | cursor.Emit(OpCodes.Ldarg, 2); 57 | cursor.Emit(OpCodes.Ldarg, 4); 58 | 59 | cursor.EmitDelegate>( 60 | (y_local, room_camera, camera_pos) => { 61 | if (room_camera.room is not Room room || room_camera.IsRoomBlacklisted(room.abstractRoom)) return y_local; 62 | return y_local + room.abstractRoom.GetFields().min_camera_position.y - camera_pos.y; // modded 63 | }); 64 | } else { 65 | if (can_log_il_hooks) { 66 | Debug.Log($"{mod_id}: IL_Water_DrawSprites failed."); 67 | } 68 | return; 69 | } 70 | // LogAllInstructions(context); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /SourceCode/ImprovedInput/PlayerMod.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace SBCameraScroll; 3 | 4 | public static class PlayerMod { 5 | // 6 | // parameters 7 | // 8 | 9 | // needs to be less than or equal to 4 given how ImprovedInput works; 10 | public static readonly int maximum_number_of_players = 4; 11 | 12 | // 13 | // variables 14 | // 15 | 16 | public static List custom_input_list = null!; 17 | 18 | // 19 | // main 20 | // 21 | 22 | internal static void OnEnable() { 23 | Initialize_Custom_Inputs(); 24 | On.Player.checkInput -= Player_CheckInput; 25 | On.Player.checkInput += Player_CheckInput; 26 | } 27 | 28 | public static void Initialize_Custom_Inputs() { 29 | if (custom_input_list != null) return; 30 | custom_input_list = new(); 31 | 32 | for (int player_number = 0; player_number < maximum_number_of_players; ++player_number) { 33 | InputPackageMod[] custom_input = new InputPackageMod[2]; 34 | custom_input[0] = new(); 35 | custom_input[1] = new(); 36 | custom_input_list.Add(custom_input); 37 | } 38 | } 39 | 40 | // 41 | // public 42 | // 43 | 44 | public static bool Wants_To_Center_Camera(this Player player) { 45 | int player_number = player.playerState.playerNumber; 46 | if (player_number < 0) return player.input[0].mp && !player.input[1].mp; 47 | if (player_number >= maximum_number_of_players) return player.input[0].mp && !player.input[1].mp; 48 | 49 | InputPackageMod[] custom_input = custom_input_list[player_number]; 50 | return custom_input[0].center_camera && !custom_input[1].center_camera; 51 | } 52 | 53 | public static bool Wants_To_Switch_Camera(this Player player) { 54 | int player_number = player.playerState.playerNumber; 55 | if (player_number < 0) return player.input[0].mp && !player.input[1].mp; 56 | if (player_number >= maximum_number_of_players) return player.input[0].mp && !player.input[1].mp; 57 | 58 | InputPackageMod[] custom_input = custom_input_list[player_number]; 59 | return custom_input[0].switch_camera && !custom_input[1].switch_camera; 60 | } 61 | 62 | // 63 | // private 64 | // 65 | 66 | private static void Player_CheckInput(On.Player.orig_checkInput orig, Player player) { 67 | // update player.input first; 68 | orig(player); 69 | 70 | int player_number = player.playerState.playerNumber; 71 | if (player_number < 0) return; 72 | if (player_number >= maximum_number_of_players) return; 73 | 74 | InputPackageMod[] custom_input = custom_input_list[player_number]; 75 | custom_input[1] = custom_input[0]; 76 | 77 | if (player.stun == 0 && !player.dead) { 78 | custom_input[0] = RWInputMod.Get_Input(player); 79 | return; 80 | } 81 | custom_input[0] = new(); 82 | } 83 | 84 | // 85 | // 86 | // 87 | 88 | public struct InputPackageMod { 89 | public bool center_camera = false; 90 | public bool switch_camera = false; 91 | public InputPackageMod() { } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /ModdedShaders/_Functions.cginc: -------------------------------------------------------------------------------- 1 | //Include file for common functions 2 | #ifndef COMMONFUNCTIONS 3 | #define COMMONFUNCTIONS 4 | static const float PI = 3.14159265; 5 | 6 | 7 | // clips by a rippleMask, depending on sideControl 8 | // sideControl: 0 = normal side, 1 = ripple side, .5 = both sides 9 | inline void rippleClip(fixed sideControl, fixed rippleMask){ 10 | if(sideControl>.5) 11 | if (rippleMask < .2) 12 | discard; 13 | if(sideControl<.5) 14 | if (rippleMask > .2) 15 | discard; 16 | } 17 | 18 | inline float get_depth01(fixed level){ 19 | level*=255; 20 | fixed sky_mask = step(255,level); 21 | level-=1; 22 | float r = fmod(level,30); 23 | return lerp(r/30,1,sky_mask); 24 | } 25 | 26 | inline float get_depth(fixed level){ 27 | level*=255; 28 | fixed sky_mask = step(255,level); 29 | level-=1; 30 | float r = fmod(level,30); 31 | return lerp(r,30,sky_mask); 32 | } 33 | 34 | inline float iLerp(float from, float to, float value){ 35 | return (value-from)/(to-from); 36 | } 37 | 38 | inline float2 iLerp(float2 from, float2 to, float2 value){ 39 | return float2(iLerp(from.x,to.x,value.x),iLerp(from.y,to.y,value.y)); 40 | } 41 | 42 | inline float2x2 rotate2d(float a) 43 | { 44 | return float2x2 ( 45 | cos(a), -sin(a), 46 | sin(a), cos(a) 47 | ); 48 | } 49 | 50 | inline float pulse(float value, float p, float a, float b){ 51 | return lerp(smoothstep(p+b,p,value),smoothstep(p-a,p,value),step(value,p)); 52 | } 53 | 54 | inline float pulse(float value, float p, float a){ 55 | return smoothstep(a,0,abs(value-p)); 56 | } 57 | 58 | inline float2 circularMotion(float a){ 59 | return float2(sin(a),cos(a)); 60 | } 61 | 62 | inline float mod(float x, float y){ //GLSL-like modulo function 63 | return x-y*floor(x/y); 64 | } 65 | 66 | inline float2 mirror(float2 uv,float2 screenSize){ 67 | float2 st = 1/screenSize; 68 | uv = abs(1-abs(uv-1)); 69 | uv = clamp(uv,st*2,1-st*2); 70 | return uv; 71 | } 72 | 73 | inline float3 rgb2hsv(float3 c) 74 | { 75 | float4 K = float4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); 76 | float4 p = lerp(float4(c.bg, K.wz), float4(c.gb, K.xy), step(c.b, c.g)); 77 | float4 q = lerp(float4(p.xyw, c.r), float4(c.r, p.yzx), step(p.x, c.r)); 78 | 79 | float d = q.x - min(q.w, q.y); 80 | float e = 1.0e-10; 81 | return float3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x); 82 | } 83 | 84 | inline float3 hsv2rgb(float3 c) 85 | { 86 | float4 K = float4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); 87 | float3 p = abs(frac(c.xxx + K.xyz) * 6.0 - K.www); 88 | return c.z * lerp(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); 89 | } 90 | 91 | inline float3 hsv2rgb(float h, float s, float v) 92 | { 93 | return hsv2rgb(float3(h,s,v)); 94 | } 95 | 96 | float lightness(float3 t) { 97 | return max(max(t.x,t.y),t.z); 98 | } 99 | 100 | float2 QuantizeToPixels(float2 coord,float2 _screenSize){ 101 | float2 pixel = 1/_screenSize; 102 | return coord - fmod(coord, pixel) + pixel * .5; 103 | } 104 | 105 | inline float4 FixEdgeShadowStretch(float2 screenPos, bool invertY){ 106 | if (invertY) screenPos.y = 1-screenPos.y; 107 | return float4(saturate(iLerp(0,.3,screenPos)),saturate(iLerp(0,.1,screenPos))); 108 | } 109 | 110 | #endif 111 | -------------------------------------------------------------------------------- /SourceCode/LevelTexCombinerMod.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace SBCameraScroll; 3 | 4 | internal static class LevelTexCombinerMod { 5 | internal static void OnEnable() { 6 | IL.Watcher.LevelTexCombiner.CreateBuffer += IL_LevelTexCombiner_CreateBuffer; 7 | IL.Watcher.LevelTexCombiner.Initialize += IL_LevelTexCombiner_Initialize; 8 | IL.Watcher.LevelTexCombiner.SetGlobals += IL_LevelTexCombiner_SetGlobals; 9 | IL.Watcher.LevelTexCombiner.UnSetGlobals += IL_LevelTexCombiner_UnSetGlobals; 10 | } 11 | 12 | // 13 | // public 14 | // 15 | 16 | public static bool LevelTexCombinerMod_ApplyLevelTexturePatch(ILCursor cursor, string function_name) { 17 | if (cursor.TryGotoNext(instruction => instruction.MatchLdfld("PersistentData", "cameraTextures"))) { 18 | if (can_log_il_hooks) { 19 | Debug.Log($"{mod_id}: IL_LevelTexCombiner_{function_name}: Index {cursor.Index}"); 20 | } 21 | 22 | cursor.Index -= 2; 23 | cursor.RemoveRange(6); 24 | 25 | cursor.Emit(OpCodes.Ldarg_0); 26 | cursor.EmitDelegate>(LevelTexCombinerMod_GetLevelTexture); 27 | 28 | } else { 29 | if (can_log_il_hooks) { 30 | Debug.Log($"{mod_id}: IL_LevelTexCombiner_{function_name} failed."); 31 | } 32 | return false; 33 | } 34 | 35 | return true; 36 | } 37 | 38 | public static UnityEngine.Texture LevelTexCombinerMod_GetLevelTexture(Watcher.LevelTexCombiner combiner) { 39 | if (Custom.rainWorld?.processManager?.currentMainLoop is not RainWorldGame game) { 40 | throw new System.Exception("[ERROR] Expected to be in-game. But I did not find the instance for RainWorldGame."); 41 | } 42 | 43 | foreach (RoomCamera room_camera in game.cameras) { 44 | if (combiner == room_camera.levelTexCombiner) { 45 | UnityEngine.Texture level_texture = room_camera.levelGraphic._atlas.texture; 46 | return level_texture; 47 | } 48 | } 49 | 50 | return game.cameras[0].levelGraphic._atlas.texture; 51 | } 52 | 53 | // 54 | // private 55 | // 56 | 57 | private static void IL_LevelTexCombiner_CreateBuffer(ILContext context) { 58 | // LogAllInstructions(context); 59 | ILCursor cursor = new ILCursor(context); 60 | if (!LevelTexCombinerMod_ApplyLevelTexturePatch(cursor, function_name: "CreateBuffer")) { 61 | return; 62 | } 63 | // LogAllInstructions(context); 64 | } 65 | 66 | private static void IL_LevelTexCombiner_Initialize(ILContext context) { 67 | // LogAllInstructions(context); 68 | ILCursor cursor = new ILCursor(context); 69 | if (!LevelTexCombinerMod_ApplyLevelTexturePatch(cursor, function_name: "Initialize")) { 70 | return; 71 | } 72 | // LogAllInstructions(context); 73 | } 74 | 75 | private static void IL_LevelTexCombiner_SetGlobals(ILContext context) { 76 | // LogAllInstructions(context); 77 | ILCursor cursor = new ILCursor(context); 78 | if (!LevelTexCombinerMod_ApplyLevelTexturePatch(cursor, function_name: "SetGlobals")) { 79 | return; 80 | } 81 | // LogAllInstructions(context); 82 | } 83 | 84 | private static void IL_LevelTexCombiner_UnSetGlobals(ILContext context) { 85 | // LogAllInstructions(context); 86 | ILCursor cursor = new ILCursor(context); 87 | if (!LevelTexCombinerMod_ApplyLevelTexturePatch(cursor, function_name: "UnSetGlobals")) { 88 | return; 89 | } 90 | // LogAllInstructions(context); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /ModdedShaders/_TerrainMask.cginc: -------------------------------------------------------------------------------- 1 | sampler2D _SlopedTerrainMask; 2 | 3 | half4 TerrainAtScrPos(float2 screenPos) 4 | { 5 | half4 result = tex2D(_SlopedTerrainMask, screenPos); 6 | result.r = 2 - result.r * 3; 7 | return result; 8 | } 9 | 10 | half4 TerrainAtLevelPos(float2 textCoord, float4 _spriteRect) 11 | { 12 | float2 screenPos = lerp(_spriteRect.xy, _spriteRect.zw, textCoord); 13 | return TerrainAtScrPos(screenPos); 14 | } 15 | 16 | half4 AddTerrain(half4 levelCol, float2 textCoord, float4 _spriteRect) 17 | { 18 | half levelDepth = (round(levelCol * 255) % 90 - 1) % 30; 19 | if (all(levelCol.rgb == 1)) 20 | levelDepth = 30; 21 | 22 | half4 terrain = TerrainAtLevelPos(textCoord, _spriteRect); 23 | half terrainDepth = round(terrain.r * 30); 24 | 25 | if (levelDepth <= terrainDepth) 26 | return levelCol; 27 | else 28 | return half4((clamp(terrainDepth, 0, 29) + 31 + (terrain.g > 0.5) * 90) / 255.0, 0, 0, 1); 29 | } 30 | 31 | half4 SampleTerrainAndLevel(sampler2D _LevelTex, float2 textCoord, float4 _spriteRect) 32 | { 33 | half4 levelCol = tex2D(_LevelTex, textCoord); 34 | return AddTerrain(levelCol, textCoord, _spriteRect); 35 | } 36 | 37 | half TerrainAndLevelDepthUnclamped(sampler2D _LevelTex, float2 textCoord, float4 _spriteRect) 38 | { 39 | half4 levelCol = tex2D(_LevelTex, textCoord); 40 | half levelDepth = fmod(round(levelCol.r * 255) - 1, 30.0); 41 | if (all(levelCol.rgb == 1)) 42 | levelDepth = 1.0 / 0.0; 43 | 44 | half4 terrain = TerrainAtLevelPos(textCoord, _spriteRect); 45 | half terrainDepth = round(terrain.r * 30); 46 | 47 | return min(levelDepth, terrainDepth); 48 | } 49 | 50 | half TerrainAndLevelDepth(sampler2D _LevelTex, float2 textCoord, float4 _spriteRect) 51 | { 52 | half4 levelCol = tex2D(_LevelTex, textCoord); 53 | int red = round(levelCol.r * 255); 54 | if (red > 90) 55 | red -= 90; 56 | 57 | half levelDepth = fmod(red - 1, 30.0); 58 | if (all(levelCol.rgb == 1)) 59 | levelDepth = 30; 60 | 61 | half4 terrain = TerrainAtLevelPos(textCoord, _spriteRect); 62 | half terrainDepth = round(terrain.r * 30); 63 | 64 | return min(levelDepth, terrainDepth); 65 | } 66 | 67 | // Discard pixels where the level, terrain curve, or objects exist 68 | void BackgroundClip(sampler2D _LevelTex, sampler2D _GrabTexture, float2 textCoord, float2 scrPos) 69 | { 70 | half4 levelCol = tex2D(_LevelTex, textCoord); 71 | 72 | // Clip when obscured by the level 73 | if (all(levelCol.rgb != 1)) 74 | discard; 75 | 76 | // Clip when obscured by objects 77 | half4 c = tex2D(_GrabTexture, scrPos); 78 | if (c.x > 1.0 / 255.0 || c.y != 0.0 || c.z != 0.0) 79 | discard; 80 | 81 | // Clip when obscured by terrain curve 82 | half4 terrain = TerrainAtScrPos(scrPos); 83 | if (terrain.r < 1.0) 84 | discard; 85 | } 86 | 87 | // Like BackgroundClip, but does not clip if the level has 100% effect color 88 | void BackgroundClipVanilla(sampler2D _LevelTex, sampler2D _GrabTexture, float2 textCoord, float2 scrPos) 89 | { 90 | half4 levelCol = tex2D(_LevelTex, textCoord); 91 | 92 | // Clip when obscured by the level 93 | if (all(levelCol.rgb != 1)) 94 | discard; 95 | 96 | // Clip when obscured by objects 97 | half4 c = tex2D(_GrabTexture, scrPos); 98 | if (c.x > 1.0 / 255.0 || c.y != 0.0 || c.z != 0.0) 99 | discard; 100 | 101 | // DON'T clip when effect color is full 102 | // OE uses this for some highlights on plants 103 | if (any(levelCol.rgb != 1)) 104 | return; 105 | 106 | // Clip when obscured by terrain curve 107 | half4 terrain = TerrainAtScrPos(scrPos); 108 | if (terrain.r < 1.0) 109 | discard; 110 | } 111 | -------------------------------------------------------------------------------- /SourceCode/RippleCameraDataMod.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace SBCameraScroll; 3 | 4 | public static class RippleCameraDataMod { 5 | internal static void OnEnable() { 6 | IL.Watcher.RippleCameraData.AddCommandBuffer += IL_RippleCameraData_AddCommandBuffer; 7 | IL.Watcher.RippleCameraData.SetGlobals += IL_RippleCameraData_SetGlobals; 8 | } 9 | 10 | // 11 | // public 12 | // 13 | 14 | public static UnityEngine.Texture RippleCameraDataMod_GetRippleTargetScreen(Watcher.RippleCameraData ripple_data) { 15 | if (Custom.rainWorld?.processManager?.currentMainLoop is not RainWorldGame game) { 16 | Debug.Log("SBCameraScroll.RippleCameraDataMod_GetRippleTargetScreen: [WARNING] Expected to be in-game. But I did not find the instance for RainWorldGame. I assume now that this is the camera for player 1 and hope for the best."); 17 | return ripple_data.rippleTargetScreen; 18 | } 19 | 20 | RoomCamera? room_camera = null; 21 | foreach (RoomCamera rc in game.cameras) { 22 | if (ripple_data == rc.rippleData) { 23 | room_camera = rc; 24 | break; 25 | } 26 | } 27 | 28 | if (room_camera == null) { 29 | Debug.Log($"SBCameraScroll.RippleCameraDataMod_GetRippleTargetScreen: [WARNING] Expected to be in-game. But I did not find the room camera for the rippleData {ripple_data}. I assume now this is the camera for player 1 and hope for the best."); 30 | return ripple_data.rippleTargetScreen; 31 | } 32 | 33 | if (RippleCameraDataMod_IsRippleRoomBlacklisted(room_camera)) { 34 | return ripple_data.rippleTargetScreen; 35 | } 36 | return room_camera.Render_Texture(); 37 | } 38 | 39 | public static bool RippleCameraDataMod_IsRippleRoomBlacklisted(RoomCamera room_camera) { 40 | string? room_name = room_camera.RippleSettings?.destRoom; 41 | if (room_name == null) { 42 | room_name = room_camera.loadingRoom?.abstractRoom.FileName; 43 | } 44 | if (room_name == null) { 45 | room_name = room_camera.room?.abstractRoom.FileName; 46 | } 47 | if (room_name == null) { 48 | Debug.Log("SBCameraScroll.RippleCameraDataMod_IsRippleRoomBlacklisted: [WARNING] Did not find any room name. Assuming the room is blacklisted."); 49 | return true; 50 | } 51 | return room_camera.IsRoomBlacklisted(room_name); 52 | } 53 | 54 | // 55 | // private 56 | // 57 | 58 | private static void IL_RippleCameraData_AddCommandBuffer(ILContext context) { 59 | // LogAllInstructions(context); 60 | ILCursor cursor = new ILCursor(context); 61 | if (!IL_RippleCameraDataMod_PatchRippleTargetScreen(cursor, function_name: "AddCommandBuffer")) { 62 | return; 63 | } 64 | // LogAllInstructions(context); 65 | } 66 | 67 | private static void IL_RippleCameraData_SetGlobals(ILContext context) { 68 | // LogAllInstructions(context); 69 | ILCursor cursor = new ILCursor(context); 70 | if (!IL_RippleCameraDataMod_PatchRippleTargetScreen(cursor, function_name: "SetGlobals")) { 71 | return; 72 | } 73 | // LogAllInstructions(context); 74 | } 75 | 76 | private static bool IL_RippleCameraDataMod_PatchRippleTargetScreen(ILCursor cursor, string function_name) { 77 | if (cursor.TryGotoNext(instruction => instruction.MatchLdfld("Watcher.RippleCameraData", "rippleTargetScreen"))) { 78 | if (can_log_il_hooks) { 79 | Debug.Log($"{mod_id}: IL_RippleCameraData_{function_name}: Index {cursor.Index}"); 80 | } 81 | 82 | cursor.RemoveRange(1); 83 | cursor.EmitDelegate>(RippleCameraDataMod_GetRippleTargetScreen); 84 | 85 | } else { 86 | if (can_log_il_hooks) { 87 | Debug.Log($"{mod_id}: IL_RippleCameraData_{function_name} failed."); 88 | } 89 | return false; 90 | } 91 | 92 | return true; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /SourceCode/AboveCloudsViewMod.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace SBCameraScroll; 3 | 4 | internal static class AboveCloudsViewMod { 5 | // copy & paste from bee's mod // removes visible "jumps" of clouds 6 | internal static void OnEnable() { 7 | On.AboveCloudsView.CloseCloud.DrawSprites += CloseCloud_DrawSprites; 8 | On.AboveCloudsView.DistantCloud.DrawSprites += DistantCloud_DrawSprites; 9 | On.AboveCloudsView.DistantLightning.DrawSprites += DistantLightning_DrawSprites; // only for adjusting alpha? 10 | On.AboveCloudsView.FlyingCloud.DrawSprites += FlyingCloud_DrawSprites; 11 | } 12 | 13 | // 14 | // private 15 | // 16 | 17 | private static void CloseCloud_DrawSprites(On.AboveCloudsView.CloseCloud.orig_DrawSprites orig, AboveCloudsView.CloseCloud close_cloud, RoomCamera.SpriteLeaser sprite_leaser, RoomCamera room_camera, float time_stacker, Vector2 camera_position) { 18 | if (room_camera.room is not Room room || room_camera.IsRoomBlacklisted(room.abstractRoom)) { 19 | orig(close_cloud, sprite_leaser, room_camera, time_stacker, camera_position); 20 | return; 21 | } 22 | 23 | Vector2 room_camera_position = room_camera.room.cameraPositions[room_camera.currentCameraPosition]; 24 | room_camera.room.cameraPositions[room_camera.currentCameraPosition] = room_camera.room.cameraPositions[0]; 25 | orig(close_cloud, sprite_leaser, room_camera, time_stacker, camera_position); 26 | room_camera.room.cameraPositions[room_camera.currentCameraPosition] = room_camera_position; 27 | 28 | // this makes the position as it should be // i.e. it respects a moving camera (even without scrolling the camera moves); 29 | // however this messes with the light of the cloud shader // the clouds look like light bulbs turning on and off when scrolling very quickly; 30 | // I need to preserve the offset (683f) for some rooms; 31 | // 32 | // at this point I think that these cloud background overlay(?) objects are too small; 33 | // scrolling them leads to problems one way or another; 34 | // I probably need to change the cloud shader instead?; 35 | // spriteLeaser.sprites[1].x += closeCloud.DrawPos(cameraPosition, roomCamera.hDisplace).x; 36 | } 37 | 38 | private static void DistantCloud_DrawSprites(On.AboveCloudsView.DistantCloud.orig_DrawSprites orig, AboveCloudsView.DistantCloud distant_cloud, RoomCamera.SpriteLeaser sprite_leaser, RoomCamera room_camera, float time_stacker, Vector2 camera_position) { 39 | if (room_camera.room is not Room room || room_camera.IsRoomBlacklisted(room.abstractRoom)) { 40 | orig(distant_cloud, sprite_leaser, room_camera, time_stacker, camera_position); 41 | return; 42 | } 43 | 44 | Vector2 room_camera_position = room_camera.room.cameraPositions[room_camera.currentCameraPosition]; 45 | room_camera.room.cameraPositions[room_camera.currentCameraPosition] = room_camera.room.cameraPositions[0]; 46 | orig(distant_cloud, sprite_leaser, room_camera, time_stacker, camera_position); 47 | room_camera.room.cameraPositions[room_camera.currentCameraPosition] = room_camera_position; 48 | 49 | // spriteLeaser.sprites[1].x += distantCloud.DrawPos(cameraPosition, roomCamera.hDisplace).x; 50 | } 51 | 52 | private static void DistantLightning_DrawSprites(On.AboveCloudsView.DistantLightning.orig_DrawSprites orig, AboveCloudsView.DistantLightning distant_lightning, RoomCamera.SpriteLeaser sprite_leaser, RoomCamera room_camera, float time_stacker, Vector2 camera_position) { 53 | if (room_camera.room is not Room room || room_camera.IsRoomBlacklisted(room.abstractRoom)) { 54 | orig(distant_lightning, sprite_leaser, room_camera, time_stacker, camera_position); 55 | return; 56 | } 57 | 58 | Vector2 room_camera_position = room_camera.room.cameraPositions[room_camera.currentCameraPosition]; 59 | room_camera.room.cameraPositions[room_camera.currentCameraPosition] = room_camera.room.cameraPositions[0]; 60 | orig(distant_lightning, sprite_leaser, room_camera, time_stacker, camera_position); 61 | room_camera.room.cameraPositions[room_camera.currentCameraPosition] = room_camera_position; 62 | 63 | // the sprites for this are already positioned correctly; 64 | // spriteLeaser.sprites[0].x -= cameraPosition.x; // wrong 65 | } 66 | 67 | private static void FlyingCloud_DrawSprites(On.AboveCloudsView.FlyingCloud.orig_DrawSprites orig, AboveCloudsView.FlyingCloud flying_cloud, RoomCamera.SpriteLeaser sprite_leaser, RoomCamera room_camera, float time_stacker, Vector2 camera_position) { 68 | if (room_camera.room is not Room room || room_camera.IsRoomBlacklisted(room.abstractRoom)) { 69 | orig(flying_cloud, sprite_leaser, room_camera, time_stacker, camera_position); 70 | return; 71 | } 72 | 73 | Vector2 room_camera_position = room_camera.room.cameraPositions[room_camera.currentCameraPosition]; 74 | room_camera.room.cameraPositions[room_camera.currentCameraPosition] = room_camera.room.cameraPositions[0]; 75 | orig(flying_cloud, sprite_leaser, room_camera, time_stacker, camera_position); 76 | room_camera.room.cameraPositions[room_camera.currentCameraPosition] = room_camera_position; 77 | 78 | // spriteLeaser.sprites[0].x += flyingCloud.DrawPos(cameraPosition, roomCamera.hDisplace).x; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /SourceCode/RainWorldMod.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace SBCameraScroll; 3 | 4 | public static class RainWorldMod { 5 | // 6 | // parameters 7 | // 8 | 9 | public static AssetBundle? modded_shaders_bundle = null; 10 | 11 | // 12 | // public 13 | // 14 | 15 | public static int? Get_Atlas_Index(string name) { 16 | FAtlasManager atlas_manager = Futile.atlasManager; 17 | List _atlases = atlas_manager._atlases; 18 | int count = Futile.atlasManager._atlases.Count; 19 | for (int i = 0; i < count; i++) { 20 | if (_atlases[i].name == name) { 21 | return i; 22 | } 23 | } 24 | return null; 25 | } 26 | 27 | public static void Load_Asset_Bundle() { 28 | if (modded_shaders_bundle != null) return; 29 | try { 30 | modded_shaders_bundle = AssetBundle.LoadFromFile($"{mod_directory_path}AssetBundles{Path.DirectorySeparatorChar}modded_shaders"); 31 | } catch (Exception exception) { 32 | Debug.Log($"{mod_id}: Could not load the asset bundle with modded shaders.\n {exception}"); 33 | modded_shaders_bundle = null; 34 | } 35 | } 36 | 37 | public static ComputeShader? Load_Compute_Shader(string shader_name) { 38 | if (modded_shaders_bundle == null) return null; 39 | ComputeShader? compute_shader = modded_shaders_bundle.LoadAsset(shader_name); 40 | if (compute_shader == null) return null; 41 | Debug.Log($"{mod_id}: Loaded the compute shader '{shader_name}'."); 42 | return compute_shader; 43 | } 44 | 45 | // We use only render textures. This function is used to replace the vanilla 46 | // Texture2D. 47 | public static void Replace_Or_Add_Atlas(string name, Texture texture) { 48 | if (Get_Atlas_Index(name) is not int index) { 49 | Futile.atlasManager.LoadAtlasFromTexture(name, texture, textureFromAsset: false); 50 | return; 51 | } 52 | 53 | FAtlasManager atlas_manager = Futile.atlasManager; 54 | if (atlas_manager._atlases[index].texture == texture) return; 55 | 56 | FAtlas atlas = new FAtlas(name, texture, index, false); // don't use index++; 57 | atlas_manager._atlases[index] = atlas; 58 | Replace_Or_Add_Atlas_Elements(atlas); 59 | Debug.Log($"{mod_id}: Replaced atlas for Texture {name}."); 60 | } 61 | 62 | public static void Replace_Or_Add_Atlas_Elements(FAtlas atlas) { 63 | int count = atlas.elements.Count; 64 | for (int i = 0; i < count; i++) 65 | { 66 | FAtlasElement atlas_element = atlas.elements[i]; 67 | atlas_element.atlas = atlas; 68 | atlas_element.atlasIndex = atlas.index; 69 | 70 | FAtlasManager atlas_manager = Futile.atlasManager; 71 | if (atlas_manager._allElementsByName.ContainsKey(atlas_element.name)) { 72 | atlas_manager._allElementsByName[atlas_element.name] = atlas_element; 73 | } else { 74 | atlas_manager._allElementsByName.Add(atlas_element.name, atlas_element); 75 | } 76 | } 77 | } 78 | 79 | public static void Replace_Shader(this RainWorld rain_world, string shader_name, string? modded_shader_name = null) { 80 | if (modded_shaders_bundle == null) return; 81 | var f_shader = FShader._shaders.Find(s => s.name == shader_name); 82 | if (f_shader == null) { 83 | Debug.Log($"{mod_id}: Didn't find the shader '{shader_name}'."); 84 | return; 85 | } 86 | 87 | modded_shader_name ??= shader_name; 88 | var modded_shader = modded_shaders_bundle.LoadAsset(modded_shader_name); 89 | if (modded_shader == null) { 90 | Debug.Log($"{mod_id}: Didn't find the modded shader for '{modded_shader_name}'."); 91 | return; 92 | } 93 | 94 | f_shader.shader = modded_shader; 95 | f_shader.name = modded_shader.name; 96 | Debug.Log($"{mod_id}: Replaced the shader '{shader_name}'."); 97 | } 98 | 99 | public static void Replace_Shader_LevelBlend() { 100 | if (modded_shaders_bundle == null) return; 101 | 102 | string shader_name = "LevelBlend"; 103 | Shader? modded_shader = modded_shaders_bundle.LoadAsset(shader_name); 104 | if (modded_shader == null) { 105 | Debug.Log($"{mod_id}: Didn't find the modded shader for '{shader_name}'."); 106 | return; 107 | } 108 | 109 | UnityEngine.Object.Destroy(Watcher.RippleCameraData.combinerMaterial); 110 | Watcher.RippleCameraData.combinerMaterial = new Material(modded_shader); 111 | Debug.Log($"{mod_id}: Replaced the shader '{shader_name}'."); 112 | } 113 | 114 | public static void Replace_Shader_SavePlayerCamoMask(Watcher.RippleCameraData ripple_data) { 115 | if (modded_shaders_bundle == null) return; 116 | 117 | string shader_name = "SavePlayerCamoMask"; 118 | Shader? modded_shader = modded_shaders_bundle.LoadAsset(shader_name); 119 | if (modded_shader == null) { 120 | Debug.Log($"{mod_id}: Didn't find the modded shader for '{shader_name}'."); 121 | return; 122 | } 123 | 124 | UnityEngine.Object.Destroy(ripple_data.playerCamoMaskSaver); 125 | ripple_data.playerCamoMaskSaver = new Material(modded_shader); 126 | Debug.Log($"{mod_id}: Replaced the shader '{shader_name}' for RippleCameraData instance {ripple_data.GetHashCode()}."); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /ModdedShaders/_RippleClip.cginc: -------------------------------------------------------------------------------- 1 | //To clip by ripple use #include "_RippleClip.cginc" 2 | //And then add rippleClip(i.scrPos); at the beginning of your fragment shader. 3 | //Keywords: 4 | //RIPPLE - main global ripple keyword, when it's enabled rippleClip keeps shader visible on the normal side by default 5 | //ripple_both_sides - local keyword, if it's enabled rippleClip keeps shader visible on both sides 6 | //ripple_other_side - local keyword, if its' enabled ripleClip keeps shader visible on the ripple side 7 | //ripple_other_side_alt - same as the previous one, but the shader is also visible through the "ripple holes" left by The Watcher 8 | //ripple_clip_distortion - overrides other keywords, clips the area where ripple distortion is visible/strong enough 9 | #ifndef RIPPLECLIP 10 | #pragma multi_compile _ RIPPLE 11 | #pragma multi_compile_local _ ripple_other_side ripple_other_side_alt ripple_both_sides ripple_clip_distortion 12 | #define RIPPLECLIP 13 | 14 | sampler2D _GameplayRippleMask; 15 | sampler2D _GameplayRipplePalTex; 16 | sampler2D _RippleMask; 17 | fixed4 _RippleColor; 18 | fixed4 _RippleGold; 19 | fixed4 _GoldRGB; 20 | 21 | inline void rippleClipDistortionOnly (float2 scrPos){ 22 | float gMask = tex2D(_GameplayRippleMask, scrPos).x; 23 | float rMask = tex2D(_RippleMask, scrPos).x; 24 | if ( gMask > .1 && gMask < .5 ) 25 | discard ; 26 | if ( rMask > .1 && rMask < .5 ) 27 | discard ; 28 | } 29 | 30 | inline void rippleClip ( float2 scrPos ) { 31 | #if ripple_clip_distortion 32 | rippleClipDistortionOnly(scrPos); 33 | return; 34 | #endif 35 | #if ripple_both_sides 36 | return ; 37 | #elif ripple_other_side 38 | if ( tex2D(_GameplayRippleMask, scrPos).y < .2 ) 39 | discard ; 40 | return ; 41 | #elif ripple_other_side_alt 42 | if ( tex2D(_GameplayRippleMask, scrPos).x < .2 ) 43 | discard ; 44 | return ; 45 | #endif 46 | #if RIPPLE 47 | if ( tex2D(_GameplayRippleMask, scrPos).y > .2 ) 48 | discard ; 49 | #endif 50 | } 51 | 52 | inline float transitionRippleColorMask (float2 scrPos){ 53 | #if RIPPLE 54 | fixed rippleMask = tex2D(_GameplayRippleMask,scrPos).y; 55 | return smoothstep(.0,.2,rippleMask); 56 | #endif 57 | return 0; 58 | } 59 | 60 | inline float allRippleColorMask (float2 scrPos){ 61 | #if RIPPLE 62 | fixed2 rippleMask = tex2D(_GameplayRippleMask,scrPos).x; 63 | return smoothstep(.0,.2,rippleMask.x); 64 | #endif 65 | return 0; 66 | } 67 | 68 | inline float rippleTrailMask (float2 scrPos){ 69 | #if RIPPLE 70 | fixed trailMask = tex2D(_RippleMask,scrPos).y; 71 | return smoothstep(.3,.9,trailMask); 72 | #endif 73 | return 0; 74 | } 75 | 76 | inline float allRippleAndTrailColorMask(float2 scrPos){ 77 | #if RIPPLE 78 | float mask = allRippleColorMask(scrPos); 79 | mask = max(rippleTrailMask(scrPos)*.8,mask); 80 | return mask; 81 | #endif 82 | return 0; 83 | } 84 | 85 | inline float rippleColorMaskKeyword(float2 scrPos){ 86 | #if RIPPLE 87 | float mask = allRippleAndTrailColorMask(scrPos); 88 | // float trail = smoothstep(.0,.4,tex2D(_RippleMask,scrPos).y); 89 | #if !(ripple_other_side || ripple_other_side_alt) 90 | mask = 1-mask; 91 | #endif 92 | return mask; 93 | #endif 94 | return 0; 95 | } 96 | 97 | ///Use on the final image 98 | inline float4 smoothRippleClip (inout fixed4 image, fixed4 color, float2 scrPos){ 99 | #if RIPPLE 100 | #if ripple_both_sides 101 | return image; 102 | #else 103 | float mask = rippleColorMaskKeyword(scrPos); 104 | image.xyz = lerp(color,image.xyz,mask); 105 | image.w *= mask; 106 | #endif 107 | #endif 108 | return image; 109 | } 110 | 111 | ///Use on the final image 112 | inline float4 smoothRippleClip (inout fixed4 image, float2 scrPos){ 113 | smoothRippleClip(image,_RippleGold*.5,scrPos); 114 | return image; 115 | } 116 | 117 | ///Use on the final image 118 | inline float4 smoothRippleClipAdditive (inout fixed4 image, fixed4 color, float2 scrPos){ 119 | #if RIPPLE 120 | #if ripple_both_sides 121 | return image; 122 | #else 123 | float mask = rippleColorMaskKeyword(scrPos); 124 | image.xyz = lerp(color*image.w,image.xyz,mask); 125 | image *= mask; 126 | #endif 127 | #endif 128 | return image; 129 | } 130 | 131 | ///Use on the final image 132 | inline float4 smoothRippleClipAdditive (inout fixed4 image, float2 scrPos){ 133 | smoothRippleClipAdditive(image,_RippleGold*.5,scrPos); 134 | return image; 135 | } 136 | 137 | inline void ApplyShiftWaveColor(inout fixed4 image, float rippleTexture, float trailTexture){ 138 | #if RIPPLE 139 | fixed gameplayShiftWave = smoothstep(.2,-.0, abs(min(rippleTexture,smoothstep(1.1,.1,trailTexture))-.2)); 140 | image = fixed4(lerp(image.xyz, image.xyz*fixed3(.85,.89,1.4), gameplayShiftWave*1.6),image.w); 141 | #endif 142 | } 143 | 144 | inline void ApplyShiftWaveColor(inout fixed4 image, float2 scrPos){ 145 | #if RIPPLE 146 | float rippleTexture = tex2D(_GameplayRippleMask,scrPos).x; 147 | float trailTexture = tex2D(_RippleMask,scrPos).y; 148 | fixed gameplayShiftWave = smoothstep(.2,-.0, abs(min(rippleTexture,smoothstep(1.1,.1,trailTexture))-.2)); 149 | image = fixed4(lerp(image.xyz, image.xyz*fixed3(.85,.89,1.4), gameplayShiftWave*1.6),image.w); 150 | #endif 151 | } 152 | 153 | fixed4 RippleSpawnColor(float2 scrPos){ 154 | fixed4 rippleColor = fixed4(_RippleGold.xyz*2,1); 155 | fixed4 goldColor = fixed4(_GoldRGB.xyz*3,.4); 156 | #if RIPPLE 157 | #if ripple_other_side 158 | return lerp(goldColor,rippleColor,transitionRippleColorMask(scrPos)); 159 | #else 160 | return lerp(rippleColor,goldColor,transitionRippleColorMask(scrPos)); 161 | #endif 162 | #endif 163 | 164 | return rippleColor; 165 | } 166 | 167 | #endif 168 | -------------------------------------------------------------------------------- /ModdedShaders/UnderWaterLight.shader: -------------------------------------------------------------------------------- 1 | // Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)' 2 | 3 | 4 | // Upgrade NOTE: replaced 'samplerRECT' with 'sampler2D' 5 | 6 | //from http://forum.unity3d.com/threads/68402-Making-a-2D-game-for-iPhone-iPad-and-need-better-performance 7 | 8 | Shader "SBCameraScroll/UnderWaterLight" //Unlit Transparent Vertex Colored Additive 9 | { 10 | Properties 11 | { 12 | _MainTex ("Base (RGB) Trans (A)", 2D) = "white" {} 13 | } 14 | 15 | Category 16 | { 17 | Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"} 18 | ZWrite Off 19 | //Alphatest Greater 0 20 | Blend SrcAlpha OneMinusSrcAlpha 21 | Fog { Color(0,0,0,0) } 22 | Lighting Off 23 | Cull Off //we can turn backface culling off because we know nothing will be facing backwards 24 | 25 | BindChannels 26 | { 27 | Bind "Vertex", vertex 28 | Bind "texcoord", texcoord 29 | Bind "Color", color 30 | } 31 | 32 | SubShader 33 | { 34 | //GrabPass { } 35 | Pass 36 | { 37 | //SetTexture [_MainTex] 38 | //{ 39 | // Combine texture * primary 40 | //} 41 | 42 | 43 | 44 | CGPROGRAM 45 | #pragma target 3.0 46 | #pragma vertex vert 47 | #pragma fragment frag 48 | #include "UnityCG.cginc" 49 | #include "_ShaderFix.cginc" 50 | #include "_BrainMoldClip.cginc" 51 | #include "_RippleClip.cginc" 52 | 53 | //#pragma profileoption NumTemps=64 54 | //#pragma profileoption NumInstructionSlots=2048 55 | 56 | //float4 _Color; 57 | sampler2D _MainTex; 58 | sampler2D _LevelTex; 59 | sampler2D _NoiseTex; 60 | //sampler2D _PalTex; 61 | //uniform float _fogAmount; 62 | uniform float _waterPosition; 63 | 64 | #if defined(SHADER_API_PSSL) 65 | sampler2D _GrabTexture; 66 | #else 67 | sampler2D _GrabTexture : register(s0); 68 | #endif 69 | 70 | uniform float _RAIN; 71 | 72 | uniform float4 _spriteRect; 73 | uniform float2 _screenSize; 74 | 75 | 76 | struct v2f { 77 | float4 pos : SV_POSITION; 78 | float2 uv : TEXCOORD0; 79 | float2 scrPos : TEXCOORD1; 80 | float4 clr : COLOR; 81 | }; 82 | 83 | float4 _MainTex_ST; 84 | 85 | v2f vert (appdata_full v) 86 | { 87 | v2f o; 88 | o.pos = UnityObjectToClipPos (v.vertex); 89 | o.uv = TRANSFORM_TEX (v.texcoord, _MainTex); 90 | o.scrPos = ComputeScreenPos(o.pos); 91 | o.clr = v.color; 92 | return o; 93 | } 94 | 95 | 96 | 97 | half4 frag (v2f i) : SV_Target 98 | { 99 | 100 | float2 textCoord = float2(floor(i.scrPos.x*_screenSize.x)/_screenSize.x, floor(i.scrPos.y*_screenSize.y)/_screenSize.y); 101 | 102 | textCoord.x -= _spriteRect.x; 103 | textCoord.y -= _spriteRect.y; 104 | textCoord.x /= _spriteRect.z - _spriteRect.x; 105 | textCoord.y /= _spriteRect.w - _spriteRect.y; 106 | 107 | half rbcol = (sin((_RAIN + (tex2D(_NoiseTex, float2(textCoord.x*1.2, textCoord.y*1.2) ).x * 3) + 0/12.0) * 3.14 * 2)*0.5)+0.5; 108 | 109 | // vanilla: 110 | // float2 distortion = float2(lerp(-0.002, 0.002, rbcol)*lerp(1, 20, pow(i.uv.y, 200)), -0.02 * pow(i.uv.y, 8)); 111 | 112 | // modded: 113 | // reduces the magnitude of the distortion effect; this can get out of hand 114 | // in larger rooms otherwise; copy&paste from DeepWater shader; 115 | // I probably can just use _LevelTex_TexelSize instead of 1/level_texture_size. 116 | float2 level_texture_size = float2((_spriteRect.z - _spriteRect.x) * _screenSize.x, (_spriteRect.w - _spriteRect.y) * _screenSize.y); 117 | float2 distortion = float2(lerp(-0.002, 0.002, rbcol) * lerp(1, 20, pow(i.uv.y, 200)) * 1400 / level_texture_size.x, -0.02 * pow(i.uv.y, 8) * 800 / level_texture_size.y); 118 | 119 | // vanilla: 120 | // distortion.x = floor(distortion.x*_screenSize.x)/_screenSize.x; 121 | // distortion.y = floor(distortion.y*_screenSize.y)/_screenSize.y; 122 | 123 | // modded: 124 | // makes the distortion less pixelated; 125 | distortion.x = floor(distortion.x * level_texture_size.x) / level_texture_size.x; 126 | distortion.y = floor(distortion.y * level_texture_size.y) / level_texture_size.y; 127 | 128 | // vanilla: 129 | half4 texcol = tex2D(_LevelTex, textCoord+distortion); 130 | 131 | //int paletteColor = floor(((int)(texcol.x * 255) % 90 )/30.0); 132 | //if(texcol.y >= 16.0/255.0) paletteColor = 3; 133 | 134 | half dist = fmod(round(texcol.x * 255)-1, 30.0)/30.0; 135 | #if RoomHasBrainMold 136 | dist = lerp(dist,.0,_BrainMoldMask(i.scrPos+distortion)); 137 | #endif 138 | if(texcol.x == 1.0 && texcol.y == 1.0 && texcol.z == 1.0) dist = 1.0; 139 | 140 | if (dist > 6.0/30.0){ 141 | half4 grabColor = tex2D(_GrabTexture, half2(i.scrPos.x, i.scrPos.y)); 142 | if( grabColor.x > 1.0/255.0 || grabColor.y != 0.0 || grabColor.z != 0.0) 143 | dist = 6.0/30.0; 144 | } 145 | 146 | 147 | half2 dir = normalize(i.uv.xy - half2(0.5, 0.5)); 148 | 149 | half centerDist = clamp(distance(i.uv.xy, half2(0.5, 0.5))*2, 0, 1); 150 | 151 | textCoord += centerDist*dir*0.02; 152 | 153 | half d = dist; 154 | 155 | 156 | if(dist < 0.2) dist = pow(1.0-(dist * 5.0), 0.35); 157 | else dist = clamp((dist - 0.2) * 1.3, 0, 1); 158 | 159 | dist = 1.0-dist; 160 | dist *= pow(pow((1-pow(min(centerDist + d*0.5, 1), 2)), 3.5), 1);//lerp(0.5, 1.5, d)); 161 | 162 | half whatToSine = (_RAIN*6) + (tex2D(_NoiseTex, float2((d/10)+lerp(textCoord.x, 0.5, d/3)*2.1, (_RAIN*0.1)+(d/5)+lerp(textCoord.y, 0.5, d/3)*2.1) ).x * 7); 163 | half col = (sin(whatToSine * 3.14 * 2)*0.5)+0.5; 164 | textCoord += dir*0.15*pow(1-centerDist, 1.7); 165 | whatToSine = (_RAIN*2.7) + (tex2D(_NoiseTex, float2((d/7)+lerp(textCoord.x, 0.5, d/5)*1.3, (_RAIN*-0.21)+(d/8)+lerp(textCoord.y, 0.5, d/6)*1.3) ).x * 6.33); 166 | half col2 = (sin(whatToSine * 3.14 * 2)*0.5)+0.5; 167 | 168 | if(pow(max(col, col2), 47) >= 0.8) 169 | dist = pow(dist, 0.6+0.4*centerDist); 170 | 171 | dist *= lerp(1, max(col, col2), pow(centerDist, 1.5)); 172 | half4 result = half4(i.clr.xyz, dist * i.clr.w * lerp(0.75, 0.5, centerDist)); 173 | smoothRippleClip(result,i.scrPos); 174 | 175 | return result; 176 | } 177 | ENDCG 178 | 179 | 180 | } 181 | } 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /ModdedShaders/Fog.shader: -------------------------------------------------------------------------------- 1 | // Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)' 2 | 3 | // This edit adds a shader variant for correct rendering with snow. 4 | // Upgrade NOTE: replaced 'samplerRECT' with 'sampler2D' 5 | 6 | //from http://forum.unity3d.com/threads/68402-Making-a-2D-game-for-iPhone-iPad-and-need-better-performance 7 | 8 | Shader "SBCameraScroll/Fog" //Unlit Transparent Vertex Colored Additive 9 | { 10 | Properties 11 | { 12 | _MainTex ("Base (RGB) Trans (A)", 2D) = "white" {} 13 | } 14 | 15 | Category 16 | { 17 | Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"} 18 | ZWrite Off 19 | //Alphatest Greater 0 20 | Blend SrcAlpha OneMinusSrcAlpha 21 | Fog { Color(0,0,0,0) } 22 | Lighting Off 23 | Cull Off //we can turn backface culling off because we know nothing will be facing backwards 24 | 25 | BindChannels 26 | { 27 | Bind "Vertex", vertex 28 | Bind "texcoord", texcoord 29 | Bind "Color", color 30 | } 31 | 32 | SubShader 33 | { 34 | // GrabPass { } 35 | 36 | Pass 37 | { 38 | //SetTexture [_MainTex] 39 | //{ 40 | // Combine texture * primary 41 | //} 42 | 43 | 44 | 45 | CGPROGRAM 46 | #pragma target 4.0 47 | #pragma vertex vert 48 | #pragma fragment frag 49 | #include "UnityCG.cginc" 50 | #include "_ShaderFix.cginc" 51 | #include "_Functions.cginc" 52 | #include "_RippleClip.cginc" 53 | #include "_Snow.cginc" 54 | 55 | //#pragma profileoption NumTemps=64 56 | //#pragma profileoption NumInstructionSlots=2048 57 | 58 | //float4 _Color; 59 | sampler2D _MainTex; 60 | sampler2D _LevelTex; 61 | sampler2D _NoiseTex; 62 | sampler2D _PalTex; 63 | //uniform float _fogAmount; 64 | uniform float _waterLevel; 65 | 66 | #if defined(SHADER_API_PSSL) 67 | sampler2D _GrabTexture; 68 | #else 69 | sampler2D _GrabTexture : register(s0); 70 | #endif 71 | 72 | uniform float _RAIN; 73 | 74 | uniform float4 _spriteRect; 75 | uniform float2 _screenSize; 76 | 77 | struct v2f { 78 | float4 pos : SV_POSITION; 79 | float2 uv : TEXCOORD0; 80 | float2 scrPos : TEXCOORD1; 81 | float4 clr : COLOR; 82 | }; 83 | 84 | float4 _MainTex_ST; 85 | 86 | v2f vert (appdata_full v) 87 | { 88 | v2f o; 89 | o.pos = UnityObjectToClipPos (v.vertex); 90 | o.uv = TRANSFORM_TEX (v.texcoord, _MainTex); 91 | o.scrPos = ComputeScreenPos(o.pos); 92 | o.clr = v.color; 93 | return o; 94 | } 95 | 96 | 97 | 98 | half4 frag (v2f i) : SV_Target 99 | { 100 | 101 | float2 textCoord = i.scrPos; 102 | 103 | textCoord.x -= _spriteRect.x; 104 | textCoord.y -= _spriteRect.y; 105 | 106 | textCoord.x /= _spriteRect.z - _spriteRect.x; 107 | textCoord.y /= _spriteRect.w - _spriteRect.y; 108 | 109 | 110 | 111 | half2 screenPos = half2(i.scrPos.x, 1-i.scrPos.y); 112 | 113 | // vanilla: 114 | // half amount = clamp((i.scrPos.y - ((1-_waterLevel) - 0.11))*3, 0, 1); 115 | 116 | // modded: 117 | // if I don't account for the size of the texture in y then the amount might 118 | // be too low at lower levels; 119 | // half amount = clamp((textCoord.y - ((1-_waterLevel) - 0.11))*3, 0, 1); 120 | half amount = clamp((textCoord.y - ((1-_waterLevel) - 0.11) / (_spriteRect.w - _spriteRect.y))*3, 0, 1); 121 | 122 | 123 | // vanilla: 124 | // half fog2 = 0.5 + 0.5f*sin((tex2D(_NoiseTex, half2(textCoord.x*0.6 + _RAIN*0.08, textCoord.y*1 - _RAIN*0.01)).x + _RAIN*0.04 + i.uv.x)*6.28); 125 | // half fog1 = 0.5 + 0.5f*sin((tex2D(_NoiseTex, half2(textCoord.x*0.44 - _RAIN*0.113, textCoord.y*1.2 - _RAIN*0.0032)).x + _RAIN*0.05 + i.uv.y)*6.28); 126 | 127 | // modded: 128 | // I need to wrap the noise texture; otherwise the fog clouds are way bigger 129 | // than intended; 130 | half fog1 = 0.5 + 0.5f*sin((tex2D(_NoiseTex, half2(textCoord.x*0.44 - _RAIN*0.113, textCoord.y*1.2 - _RAIN*0.0032)).x + _RAIN*0.05 + textCoord.y)*6.28); 131 | half fog2 = 0.5 + 0.5f*sin((tex2D(_NoiseTex, half2(textCoord.x*0.6 + _RAIN*0.08, textCoord.y*1 - _RAIN*0.01)).x + _RAIN*0.04 + textCoord.x)*6.28); 132 | 133 | // displace = pow(displace * displace2, 0.5);//lerp(displace, displace2, 0.5); 134 | fog1 = lerp(fog1, fog2, 0.5); 135 | half4 texcol = tex2D(_LevelTex, textCoord); 136 | texcol = AddSnow(texcol, textCoord,i.scrPos); 137 | half dp = fmod(round(texcol.x * 255)-1, 30.0)/30.0; 138 | if(texcol.x == 1 && texcol.y == 1 && texcol.z == 1) 139 | dp = 1; 140 | 141 | if(dp > 6.0/30.0){ 142 | half4 grabTexCol = tex2D(_GrabTexture, half2(i.scrPos.x, i.scrPos.y)); 143 | if (grabTexCol.x > 1.0/255.0 || grabTexCol.y != 0.0 || grabTexCol.z != 0.0) 144 | dp = 6.0/30.0; 145 | } 146 | 147 | if(dp >= .99999997){ 148 | // vanilla: 149 | // fog2 = 0.5 + 0.5f*sin((tex2D(_NoiseTex, half2(i.uv.x*1.7 + _RAIN*0.113, i.uv.y*2.82)).x + _RAIN*0.14 - i.uv.x)*6.28); 150 | // fog2 *= clamp(1-distance(i.uv, half2(0,0.9)), 0, 1); 151 | 152 | // modded: 153 | fog2 = 0.5 + 0.5f*sin((tex2D(_NoiseTex, half2(textCoord.x*1.7 + _RAIN*0.113, textCoord.y*2.82)).x + _RAIN*0.14 - textCoord.x)*6.28); 154 | fog2 *= clamp(1-distance(textCoord, half2(0,0.9)), 0, 1); 155 | 156 | fog2 = pow(fog2, 0.2); 157 | fog2 *= amount; 158 | fog2 *= 1 - pow(fog1, 1.5); 159 | fog2 *= i.clr.w; 160 | if(fog2 > 0.5){ 161 | half4 result = lerp(tex2D(_PalTex, float2(0, 7.0/8.0)), half4(1,1,1,1), fog2 > 0.6 ? 0.25 : 0.1); 162 | smoothRippleClip(result, i.scrPos); 163 | return result; 164 | } 165 | } 166 | 167 | fog1 = pow(fog1, 3); 168 | fog1 *= i.clr.w; 169 | 170 | //fog1 *= min(dp+0.1, 1); 171 | fog1 = pow(fog1, 1 + (1-pow(dp, 0.1))*30); 172 | fog1 = max(0, fog1 - (1-amount)); 173 | 174 | fog1 = pow(fog1, 0.2); 175 | 176 | //return half4(fog1, fog1, 0, 1); 177 | 178 | 179 | 180 | if(fog1 > 0.1){ 181 | half4 result = half4(lerp(tex2D(_PalTex, float2(0, 2.0/8.0)), tex2D(_PalTex, float2(0, 7.0/8.0)), 0.5+0.5*dp).xyz, fog1 > 0.5 ? 0.6 : 0.2); 182 | smoothRippleClip(result,i.scrPos); 183 | return result; 184 | } 185 | else return half4(0,0,0,0); 186 | 187 | //return lerp(tex2D(_GrabTexture, float2(screenPos.x, screenPos.y)), half4(displace,0,0,1), 1);//amount*0.75); 188 | // 189 | } 190 | ENDCG 191 | 192 | 193 | 194 | } 195 | } 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /ModdedShaders/SporesSnow.shader: -------------------------------------------------------------------------------- 1 | // Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)' 2 | // Edit of the Spores.shader for snow 3 | 4 | // Upgrade NOTE: replaced 'samplerRECT' with 'sampler2D' 5 | 6 | //from http://forum.unity3d.com/threads/68402-Making-a-2D-game-for-iPhone-iPad-and-need-better-performance 7 | 8 | Shader "SBCameraScroll/SporesSnow" //Unlit Transparent Vertex Colored Additive 9 | { 10 | Properties 11 | { 12 | _MainTex ("Base (RGB) Trans (A)", 2D) = "white" {} 13 | } 14 | 15 | Category 16 | { 17 | Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"} 18 | ZWrite Off 19 | //Alphatest Greater 0 20 | Blend SrcAlpha OneMinusSrcAlpha 21 | Fog { Color(0,0,0,0) } 22 | Lighting Off 23 | Cull Off //we can turn backface culling off because we know nothing will be facing backwards 24 | 25 | BindChannels 26 | { 27 | Bind "Vertex", vertex 28 | Bind "texcoord", texcoord 29 | Bind "Color", color 30 | } 31 | 32 | SubShader 33 | { 34 | Pass 35 | { 36 | //SetTexture [_MainTex] 37 | //{ 38 | // Combine texture * primary 39 | //} 40 | 41 | 42 | 43 | CGPROGRAM 44 | #pragma target 4.0 45 | #pragma vertex vert 46 | #pragma fragment frag 47 | #pragma multi_compile __ HR 48 | #include "UnityCG.cginc" 49 | #include "_ShaderFix.cginc" 50 | 51 | //#pragma profileoption NumTemps=64 52 | //#pragma profileoption NumInstructionSlots=2048 53 | 54 | //float4 _Color; 55 | sampler2D _MainTex; 56 | sampler2D _LevelTex; 57 | sampler2D _NoiseTex; 58 | sampler2D _PalTex; 59 | uniform float _fogAmount; 60 | float4 _lightDirAndPixelSize; 61 | float _cloudsSpeed; 62 | float _light = 0; 63 | sampler2D _SnowTex; 64 | //uniform float _waterPosition; 65 | 66 | //#if defined(SHADER_API_PSSL) 67 | //sampler2D _GrabTexture; 68 | //#else 69 | //sampler2D _GrabTexture : register(s0); 70 | //#endif 71 | 72 | uniform float _RAIN; 73 | 74 | uniform float4 _spriteRect; 75 | uniform float2 _screenSize; 76 | 77 | 78 | struct v2f { 79 | float4 pos : SV_POSITION; 80 | float2 uv : TEXCOORD0; 81 | float2 scrPos : TEXCOORD1; 82 | float4 clr : COLOR; 83 | }; 84 | 85 | float4 _MainTex_ST; 86 | 87 | v2f vert (appdata_full v) 88 | { 89 | v2f o; 90 | o.pos = UnityObjectToClipPos (v.vertex); 91 | o.uv = TRANSFORM_TEX (v.texcoord, _MainTex); 92 | o.scrPos = ComputeScreenPos(o.pos); 93 | o.clr = fixed4(v.color.xyz,v.color.w*tex2Dlod(_SnowTex,float4(v.color.xy,0,0)).y); 94 | return o; 95 | } 96 | 97 | float GetShadows (float a) 98 | { 99 | // if (a==1.0) return 1; 100 | a*=255; 101 | return (step(round(a),90)*-1+1); 102 | } 103 | 104 | half4 frag (v2f i) : SV_Target 105 | { 106 | float2 textCoord2 = float2(floor(i.scrPos.x*_screenSize.x)/_screenSize.x, floor(i.scrPos.y*_screenSize.y + _RAIN)/_screenSize.y); 107 | textCoord2.x -= _spriteRect.x; 108 | textCoord2.y -= _spriteRect.y; 109 | textCoord2.x /= _spriteRect.z - _spriteRect.x; 110 | textCoord2.y /= _spriteRect.w - _spriteRect.y; 111 | float2 textCoord = float2(floor(i.scrPos.x*_screenSize.x)/_screenSize.x, floor(i.scrPos.y*_screenSize.y + _RAIN*153.2)/_screenSize.y); 112 | 113 | //textCoord.y += _RAIN*0.02; 114 | 115 | textCoord.x -= _spriteRect.x; 116 | textCoord.y -= _spriteRect.y; 117 | 118 | textCoord.x /= _spriteRect.z - _spriteRect.x; 119 | textCoord.y /= _spriteRect.w - _spriteRect.y; 120 | 121 | textCoord.y += 0.04; 122 | 123 | float dist = clamp(1-distance(i.uv.xy, half2(0.5, 0.5))*2, 0, 1); 124 | 125 | 126 | 127 | half h = (sin((1.77 * _RAIN + tex2D(_NoiseTex, float2(textCoord.x*5.2, _RAIN * - 0.1 + textCoord.y*2.6) ).x * 3) * 3.14 * 2)*0.5)+0.5; 128 | h *= (sin((3.5 * _RAIN + tex2D(_NoiseTex, float2(textCoord.x*12.2, _RAIN * - 0.25 + textCoord.y*6.6) ).x * 3) * 3.14 * 2)*0.5)+0.5; 129 | 130 | 131 | h *= 0.5 + 0.5 * sin((tex2D(_NoiseTex, i.uv.xy).x + _RAIN)*6.28*3); 132 | // return half4(h, h, h, 1); 133 | 134 | 135 | h = lerp(h*dist, lerp(h, 1, lerp(0.3,0.8,i.clr.w)), dist); 136 | 137 | //half2 randomCoord = half2(textCoord.x, textCoord.y-_RAIN); 138 | 139 | float rand = frac(sin(dot(textCoord.x, 12.98232)+textCoord.y-tex2D(_NoiseTex, textCoord).x) * 43758.5453); 140 | 141 | h -= rand*lerp(0.7, 0.3, i.clr.w); 142 | float shadows = GetShadows(tex2D(_LevelTex,textCoord2+ float2(_lightDirAndPixelSize.x*_lightDirAndPixelSize.z*(6*h),-_lightDirAndPixelSize.y*_lightDirAndPixelSize.z*(8*h))).x); 143 | //h*=dist; 144 | 145 | //////////////RAINWORLD SHADOWS 146 | // vanilla: 147 | // half shadow = tex2D(_NoiseTex, float2((textCoord2.x*0.5) + (_RAIN*0.1*_cloudsSpeed) - (0.003*6*h), 1-(textCoord2.y*0.5) + (_RAIN*0.2*_cloudsSpeed) - (0.003*6*h))).x; 148 | // shadow = 0.5 + sin(fmod(shadow+(_RAIN*0.1*_cloudsSpeed)-textCoord2.y, 1)*3.14*2)*0.5; 149 | 150 | // modded: 151 | // The clouds light mask can be stretched. The mask is 152 | // applied to the full level texture. Merged level 153 | // textures contain multiple screens. 154 | float number_of_screens_x = _spriteRect.z - _spriteRect.x; 155 | float number_of_screens_y = _spriteRect.w - _spriteRect.y; 156 | half shadow = tex2D(_NoiseTex, float2((number_of_screens_x*textCoord2.x*0.5) + (_RAIN*0.1*_cloudsSpeed) - (0.003*6*h), 1-(number_of_screens_y*textCoord2.y*0.5) + (_RAIN*0.2*_cloudsSpeed) - (0.003*6*h))).x; 157 | shadow = 0.5 + sin(fmod(shadow+(_RAIN*0.1*_cloudsSpeed)-number_of_screens_y*textCoord2.y, 1)*3.14*2)*0.5; 158 | 159 | // vanilla: 160 | shadow = clamp(((shadow - 0.5)*6)+0.5-(_light*4), 0,1); 161 | 162 | //////////////CONTINUE 163 | fixed4 fog = tex2D(_PalTex, float2(1.5/32.0, 7.5/8.0)); 164 | 165 | fixed4 snow = tex2D(_PalTex,float2(6*0.03333*0.9375,0.125+0.125)); 166 | fixed4 snowLight = tex2D(_PalTex,float2(6*0.03333*0.9375,0.57+0.125)); 167 | #if HR 168 | snow = lerp(snow,snowLight,(-shadow+1)*shadows); 169 | snow+=.05; 170 | snow = lerp(snow,fog,_fogAmount*(6/30)); 171 | #else 172 | snow = lerp(snow,snowLight,(-shadow+1)*shadows); 173 | snow+=.3; 174 | snow = lerp(snow,fog,_fogAmount*(6/30)); 175 | #endif 176 | 177 | 178 | // return shadows; 179 | // return float4(textCoord2.xy,0,1); 180 | // return i.clr+fixed4(0,0,0,1); 181 | 182 | if(h * i.clr.w < 0.35) 183 | return float4(0, 0, 0, 0); 184 | 185 | if(h * i.clr.w > 0.5) 186 | return half4(snow.xyz*0.7+.2,1); 187 | 188 | return half4(snow.xyz,1);//rand, rand, rand, 1); 189 | 190 | } 191 | ENDCG 192 | 193 | 194 | 195 | } 196 | } 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /ModdedShaders/LevelBlend.shader: -------------------------------------------------------------------------------- 1 | Shader "SBCameraScroll/LevelBlend" 2 | { 3 | Properties 4 | { 5 | _MainTex ("Texture", 2D) = "white" {} 6 | } 7 | SubShader 8 | { 9 | Tags {"Queue"="Transparent" "IgnoreProjector"="False" "RenderType"="Transparent"} 10 | // Blend SrcAlpha OneMinusSrcAlpha 11 | //Alphatest Greater 0 12 | Blend Off 13 | Lighting Off 14 | Cull Off 15 | BindChannels 16 | { 17 | Bind "Vertex", vertex 18 | Bind "texcoord", texcoord 19 | Bind "Color", color 20 | } 21 | Pass 22 | { 23 | CGPROGRAM 24 | #pragma target 4.0 25 | #pragma vertex vert 26 | #pragma fragment frag 27 | #pragma multi_compile _ GAMEPLAYRIPPLETEXTURE 28 | #include "UnityCG.cginc" 29 | #include "_ShaderFix.cginc" 30 | #include "_Functions.cginc" 31 | 32 | struct appdata 33 | { 34 | float4 vertex : POSITION; 35 | float2 uv : TEXCOORD0; 36 | }; 37 | 38 | struct v2f 39 | { 40 | float2 uv : TEXCOORD0; 41 | float4 pos : SV_POSITION; 42 | float2 uv2 : TEXCOORD1; 43 | float4 clr : COLOR; 44 | float2 grainCoord : TEXCOORD2; 45 | }; 46 | sampler2D _MainTex; 47 | float2 _MainTex_TexelSize; 48 | float4 _MainTex_ST; 49 | sampler2D _LevelTex; 50 | float2 _screenSize; 51 | float4 _spriteRect; 52 | float2 _SlowFollowCreatureScreenPos; 53 | float _RAIN; 54 | sampler2D _RippleMask; 55 | sampler2D _GameplayRippleMask; 56 | sampler2D _RippleMask_TexelSize; 57 | sampler2D _GameplayRippleTexture; 58 | sampler2D _PlayerCamoMaskSaved; 59 | sampler2D _UniNoise; 60 | 61 | float2 scale_from(float2 uv, float2 p, float scale) { 62 | float2 offsetUV = uv - p; 63 | offsetUV *= scale; 64 | return offsetUV + p - uv; 65 | } 66 | 67 | v2f vert (appdata_full v) 68 | { 69 | v2f o; 70 | o.pos = UnityObjectToClipPos (v.vertex); 71 | 72 | // This is from zero to one over the full room texture. 73 | o.uv = TRANSFORM_TEX(v.texcoord, _MainTex); 74 | 75 | o.clr = v.color; 76 | 77 | // This is more like a fullscreen effect. It is zero to one over 78 | // the screen size. (For example in x, at the left edge of the 79 | // screen the value is always <= 0 and at the right side it is 80 | // always >= 1. Negative values are treated like zero and values 81 | // >= 1 are like one. Moving the character (scrolling) changes 82 | // the values but the bounds don't change.) 83 | o.uv2 = lerp(_spriteRect.xy,_spriteRect.zw,o.uv); 84 | 85 | o.grainCoord = o.uv*fixed2(6,4); 86 | return o; 87 | } 88 | 89 | fixed step3(fixed3 a, fixed3 b){ 90 | return saturate(step(a.x,b.x)+step(a.y,b.y)+step(a.z,b.z)); 91 | } 92 | 93 | fixed4 frag (v2f i) : SV_Target 94 | { 95 | // modded: 96 | // We need to scale the distortion effect when using full room 97 | // textures. Otherwise the distortion scales with the number of 98 | // screens. Without using camera scroll is this roughtly one. 99 | // 100 | // There is still a small mismatch. In WRSA_L01 you should stand 101 | // normally on top of the pillar structure instead of slightly 102 | // clipping into the ground. 103 | float2 distortion_scale = _spriteRect.zw - _spriteRect.xy; 104 | 105 | fixed3 shiftTex = tex2D(_RippleMask,i.uv2); 106 | fixed playerMask = step3(tex2D(_PlayerCamoMaskSaved,i.uv2).xyz,.00001); 107 | fixed gameplayShiftTex = tex2D(_GameplayRippleMask,i.uv2).x; 108 | 109 | _SlowFollowCreatureScreenPos = saturate(_SlowFollowCreatureScreenPos); 110 | fixed gameplayShiftMask = lerp(gameplayShiftTex,shiftTex.y*.2,shiftTex.y*1.1); 111 | 112 | float2 tailDistortion =scale_from(i.uv.xy,fixed2(.5,.5),1-shiftTex.y*.1+saturate(shiftTex.y-.8)*.8); 113 | tailDistortion *=playerMask; 114 | #if GAMEPLAYRIPPLETEXTURE 115 | float2 sourceDistortion =scale_from(i.uv.xy,_SlowFollowCreatureScreenPos,1-max(shiftTex.x,gameplayShiftTex)*.5); 116 | #else 117 | float2 sourceDistortion =scale_from(i.uv.xy,_SlowFollowCreatureScreenPos,1-max(shiftTex.x,smoothstep(.2,-.0,abs(gameplayShiftTex-.25))*.5-saturate(gameplayShiftTex-.2))*.5); 118 | #endif 119 | float2 destDistortion =scale_from(i.uv.xy,_SlowFollowCreatureScreenPos,1-saturate(smoothstep(0.3,0,lerp(shiftTex.x,gameplayShiftTex*.25+saturate(gameplayShiftTex-.3),gameplayShiftTex>.2)))*.25); 120 | 121 | // vanilla: 122 | // fixed4 source = tex2D(_LevelTex,i.uv+sourceDistortion+tailDistortion); 123 | // fixed4 destination = tex2D(_MainTex,i.uv+destDistortion+tailDistortion); 124 | 125 | // modded: 126 | fixed4 source = tex2D(_LevelTex,i.uv+(sourceDistortion+tailDistortion)/distortion_scale); 127 | fixed4 destination = tex2D(_MainTex,i.uv+(destDistortion+tailDistortion)/distortion_scale); 128 | 129 | #if GAMEPLAYRIPPLETEXTURE 130 | // vanilla: 131 | // fixed4 gameplayDest = tex2D(_GameplayRippleTexture,i.uv+destDistortion+tailDistortion); 132 | 133 | // modded: 134 | fixed4 gameplayDest = tex2D(_GameplayRippleTexture,i.uv+(destDistortion+tailDistortion)/distortion_scale); 135 | 136 | if (gameplayShiftMask>.2){ 137 | if (shiftTex.z>0.5&&get_depth(gameplayDest).x<5) return (.029); 138 | return gameplayDest; 139 | } 140 | #endif 141 | float grain = abs(fmod(tex2D(_UniNoise,i.grainCoord*fixed2(6,4)).x+_RAIN*4,1)-.5)*2;//grain to fuzzy out any other visible hard edges 142 | if (shiftTex.x>.10+grain*.03){ 143 | float destDepth = get_depth(destination); 144 | if (shiftTex.z>0.5&&destDepth<5) return (.029); 145 | float mix = smoothstep(.1,.20-grain*.01,shiftTex.x); 146 | destination.x +=-destDepth/255+trunc(lerp(get_depth(source),destDepth,mix))/255;// Interpolate depth to smooth out the transition 147 | return destination; 148 | } 149 | return source; 150 | } 151 | ENDCG 152 | } 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /ModdedShaders/DeepProcessing.shader: -------------------------------------------------------------------------------- 1 | // Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)' 2 | 3 | // Upgrade NOTE: replaced 'samplerRECT' with 'sampler2D' 4 | 5 | //from http://forum.unity3d.com/threads/68402-Making-a-2D-game-for-iPhone-iPad-and-need-better-performance 6 | 7 | Shader "SBCameraScroll/DeepProcessing" //Unlit Transparent Vertex Colored Additive 8 | { 9 | Properties 10 | { 11 | _MainTex ("Base (RGB) Trans (A)", 2D) = "white" {} 12 | } 13 | 14 | Category 15 | { 16 | Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"} 17 | ZWrite Off 18 | //Alphatest Greater 0 19 | Blend SrcAlpha OneMinusSrcAlpha 20 | Fog { Color(0,0,0,0) } 21 | Lighting Off 22 | Cull Off //we can turn backface culling off because we know nothing will be facing backwards 23 | 24 | BindChannels 25 | { 26 | Bind "Vertex", vertex 27 | Bind "texcoord", texcoord 28 | Bind "Color", color 29 | } 30 | 31 | SubShader 32 | { 33 | Pass 34 | { 35 | //SetTexture [_MainTex] 36 | //{ 37 | // Combine texture * primary 38 | //} 39 | 40 | 41 | 42 | CGPROGRAM 43 | #pragma target 3.0 44 | #pragma vertex vert 45 | #pragma fragment frag 46 | #include "UnityCG.cginc" 47 | #include "_ShaderFix.cginc" 48 | 49 | //#pragma profileoption NumTemps=64 50 | //#pragma profileoption NumInstructionSlots=2048 51 | 52 | //float4 _Color; 53 | sampler2D _MainTex; 54 | sampler2D _LevelTex; 55 | sampler2D _NoiseTex; 56 | sampler2D _PalTex; 57 | //uniform float _fogAmount; 58 | //uniform float _waterPosition; 59 | 60 | #if defined(SHADER_API_PSSL) 61 | sampler2D _GrabTexture; 62 | #else 63 | sampler2D _GrabTexture : register(s0); 64 | #endif 65 | 66 | uniform float _RAIN; 67 | 68 | uniform float4 _spriteRect; 69 | uniform float2 _screenSize; 70 | 71 | 72 | struct v2f { 73 | float4 pos : SV_POSITION; 74 | float2 uv : TEXCOORD0; 75 | float2 scrPos : TEXCOORD1; 76 | float4 clr : COLOR; 77 | }; 78 | 79 | float4 _MainTex_ST; 80 | 81 | v2f vert (appdata_full v) 82 | { 83 | v2f o; 84 | o.pos = UnityObjectToClipPos (v.vertex); 85 | o.uv = TRANSFORM_TEX (v.texcoord, _MainTex); 86 | o.scrPos = ComputeScreenPos(o.pos); 87 | o.clr = v.color; 88 | return o; 89 | } 90 | 91 | 92 | 93 | half4 frag (v2f i) : SV_Target 94 | { 95 | 96 | float2 textCoord = float2(floor(i.scrPos.x*_screenSize.x)/_screenSize.x, floor(i.scrPos.y*_screenSize.y)/_screenSize.y); 97 | 98 | textCoord.x -= _spriteRect.x; 99 | textCoord.y -= _spriteRect.y; 100 | 101 | textCoord.x /= _spriteRect.z - _spriteRect.x; 102 | textCoord.y /= _spriteRect.w - _spriteRect.y; 103 | 104 | float4 grabTexCol; 105 | 106 | half dist = 1.0-clamp(distance(i.uv.xy, half2(0.5, 0.5))*2, 0, 1); 107 | //dist = pow(pow((1-pow(dist , 2)), 3.5), 1); 108 | 109 | half4 texcol = tex2D(_LevelTex, textCoord); 110 | half dp = fmod(round(texcol.x * 255)-1, 30.0)/30.0; 111 | if(texcol.x == 1.0 && texcol.y == 1.0 && texcol.z == 1.0) dp = 1; 112 | 113 | 114 | if(dp < i.clr.x) return half4(0,0,0,0); 115 | else if(dp > i.clr.y) return half4(0,0,0,0); 116 | else if(dp > 6.0/30.0){ 117 | grabTexCol = tex2D(_GrabTexture, half2(i.scrPos.x, i.scrPos.y)); 118 | if (grabTexCol.x > 1.0/255.0 || grabTexCol.y != 0.0 || grabTexCol.z != 0.0) return half4(0,0,0,0); 119 | } 120 | 121 | half2 dsplace = (half2(i.scrPos.x, 1.0-i.scrPos.y) - half2(0.5, 0.5)) * 0.06 * dp; 122 | 123 | // vanilla: 124 | // half gridSize = lerp(10, 6, dp); 125 | 126 | // modded: 127 | // Is this shader used in non-square rooms? 128 | // 129 | // The variable sizeModifier is resolution dependent. It is the number of 130 | // screen widths that fit into the merged level texture. But honestly it looks 131 | // better if it is not too small. There are two cases, 132 | // 1) you can zoom out by increasing the resolution and 133 | // 2) you can fit the content on a larger monitor by increasing the 134 | // resolution and countering the zoom by changing the camera_zoom as well. 135 | // In case 2, I expect this to be too large. But in case 1, you get the same 136 | // size as before, i.e. the grid gets larger but you also zoom out. I leave it 137 | // as is. The resolution is intended to be used to zoom out and not to support 138 | // larger monitors. 139 | float sizeModifier = _spriteRect.z - _spriteRect.x; 140 | half gridSize = lerp(10, 6, dp) / sizeModifier; 141 | 142 | 143 | half2 sampleCoord = textCoord; 144 | sampleCoord *= half2(_screenSize.x, _screenSize.y); 145 | sampleCoord /= gridSize; 146 | 147 | sampleCoord.x = floor(sampleCoord.x)/_screenSize.x; 148 | sampleCoord.y = floor(sampleCoord.y)/_screenSize.y; 149 | sampleCoord *= gridSize; 150 | 151 | sampleCoord += dsplace; 152 | 153 | // vanilla: 154 | // float rand = frac(sin(dot(sampleCoord.x, 12.98232)*sampleCoord.y*0.23532+_RAIN-tex2D(_NoiseTex, half2(lerp(sampleCoord.y, lerp(0.265, 0.9455, sampleCoord.x), 0.5), _RAIN)).x) * 43758.5453); 155 | // half h2 = (sin((dp * 1.2 + 0.75 * _RAIN * lerp(0.5, 1.5, i.clr.w) + tex2D(_NoiseTex, float2(sampleCoord.x*1.2, sampleCoord.y*0.6) ).x * lerp(1, 1, pow(dist, 0.5))) * 3.14 * 2)*0.5)+0.5; 156 | // half h = (sin((dp * lerp(0.12, 2.2, h2) + 1.8 * _RAIN * lerp(0.5, 1.5, i.clr.w) + tex2D(_NoiseTex, float2(sampleCoord.x*8.2, sampleCoord.y*4.2) ).x * lerp(1,4,pow(h2,3))) * 3.14 * 2)*0.5)+0.5; 157 | 158 | // modded: 159 | // Increase the density for the randomness. It smears the outlines for the grid 160 | // blocks too much otherwise. 161 | float rand = frac(sin(dot(sampleCoord.x, 12.98232)*sampleCoord.y*0.23532+_RAIN-tex2D(_NoiseTex, half2(lerp(sampleCoord.y * sizeModifier, lerp(0.265, 0.9455, sampleCoord.x * sizeModifier), 0.5), _RAIN)).x) * 43758.5453); 162 | half h2 = (sin((dp * 1.2 + 0.75 * _RAIN * lerp(0.5, 1.5, i.clr.w) + tex2D(_NoiseTex, float2(sampleCoord.x * sizeModifier *1.2, sampleCoord.y * sizeModifier *0.6) ).x * lerp(1, 1, pow(dist, 0.5))) * 3.14 * 2)*0.5)+0.5; 163 | half h = (sin((dp * lerp(0.12, 2.2, h2) + 1.8 * _RAIN * lerp(0.5, 1.5, i.clr.w) + tex2D(_NoiseTex, float2(sampleCoord.x * sizeModifier *8.2, sampleCoord.y * sizeModifier *4.2) ).x * lerp(1,4,pow(h2,3))) * 3.14 * 2)*0.5)+0.5; 164 | 165 | if(lerp(h, 0, 1.0-i.clr.z) < rand * (1.0-i.clr.z)) return half4(0,0,0,0); 166 | 167 | h *= 1.0-distance(textCoord + dsplace, sampleCoord + half2(gridSize*0.5/_screenSize.x, gridSize*0.5/_screenSize.y)) * (_screenSize.x + _screenSize.y) / (gridSize*2); 168 | 169 | //return half4(h, h, h, 1); 170 | 171 | dist = pow(dist, lerp(1.2, lerp(0.199, 0.001, i.clr.w), h2)); 172 | 173 | h += lerp(-1, 1, i.clr.w)*0.2; 174 | 175 | if(h*dist < 0.33) return half4(0,0,0,0); 176 | else if (h*lerp(dist,1,0.5) < 0.66) return half4(0,0,0.5,0.2); 177 | 178 | return half4(0,0,1,0.4);//tex2D(_PalTex, half2(31.5/32.0, 4.5/8.0)); 179 | 180 | // return half4(dist, dist, dist, 1); 181 | 182 | } 183 | ENDCG 184 | 185 | 186 | 187 | } 188 | } 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /SourceCode/MoreSlugcatsMod.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace SBCameraScroll; 3 | 4 | internal static class MoreSlugcatsMod { 5 | internal static void OnEnable() { 6 | IL.MoreSlugcats.BlizzardGraphics.DrawSprites += IL_BlizzardGraphics_DrawSprites; 7 | On.MoreSlugcats.BlizzardGraphics.Update += BlizzardGraphics_Update; 8 | On.MoreSlugcats.SnowSource.PackSnowData += SnowSource_PackSnowData; 9 | On.MoreSlugcats.SnowSource.Update += SnowSource_Update; 10 | } 11 | 12 | // 13 | // private 14 | // 15 | 16 | private static void IL_BlizzardGraphics_DrawSprites(ILContext context) { 17 | // MainMod.LogAllInstructions(context); 18 | 19 | ILCursor cursor = new(context); 20 | if (cursor.TryGotoNext(instruction => instruction.MatchLdarg(2))) { 21 | if (can_log_il_hooks) { 22 | Debug.Log($"{mod_id}: IL_BlizzardGraphics_DrawSprites: Index {cursor.Index}"); // 16 23 | } 24 | 25 | cursor.Next.OpCode = OpCodes.Ldarg_0; // blizzardGraphics 26 | cursor.GotoNext(); 27 | cursor.RemoveRange(9); 28 | cursor.Emit(OpCodes.Ldarg, 4); // cameraPosition 29 | 30 | cursor.EmitDelegate>((blizzard_graphics, camera_position) => { 31 | RoomCamera room_camera = blizzard_graphics.rCam; 32 | if (room_camera.room is not Room room || room_camera.IsRoomBlacklisted(room.abstractRoom)) { 33 | return room_camera.pos - blizzard_graphics.room.cameraPositions[room_camera.currentCameraPosition]; 34 | } 35 | 36 | // jumps but I can't cover the whole room otherwise..; 37 | // the size of blizzardGraphics is probably determined by the shader; 38 | // if you scale the sprites then the snow flakes are scaled instead; 39 | return camera_position - blizzard_graphics.room.cameraPositions[room_camera.currentCameraPosition]; 40 | }); 41 | } else { 42 | if (can_log_il_hooks) { 43 | Debug.Log($"{mod_id}: IL_BlizzardGraphics_DrawSprites failed."); 44 | } 45 | return; 46 | } 47 | // MainMod.LogAllInstructions(context); 48 | } 49 | 50 | // 51 | // 52 | // 53 | 54 | private static void BlizzardGraphics_Update(On.MoreSlugcats.BlizzardGraphics.orig_Update orig, MoreSlugcats.BlizzardGraphics blizzard_graphics, bool eu) { 55 | RoomCamera room_camera = blizzard_graphics.rCam; 56 | if (room_camera.room is not Room room || room_camera.IsRoomBlacklisted(room.abstractRoom)) { 57 | orig(blizzard_graphics, eu); 58 | return; 59 | } 60 | 61 | // prevent jumps when currentCameraPositionIndex changes 62 | // this does not seem to change the position; 63 | // the jumps in DrawSprites have a purpose; 64 | // because the whole thing seems to be fixed in size; 65 | // I can only move it around; 66 | // 67 | // these here seem to don't have that; they have see below; 68 | float room_camera_position_x = room_camera.room.cameraPositions[room_camera.currentCameraPosition].x; 69 | 70 | // this is a compromise (only change x); 71 | // this results in extra jumps for the snow; 72 | // but otherwise show might fall through the ceiling for some screens; 73 | 74 | room_camera.room.cameraPositions[room_camera.currentCameraPosition].x = room_camera.room.cameraPositions[0].x; 75 | orig(blizzard_graphics, eu); 76 | room_camera.room.cameraPositions[room_camera.currentCameraPosition].x = room_camera_position_x; 77 | } 78 | 79 | private static Vector4[] SnowSource_PackSnowData(On.MoreSlugcats.SnowSource.orig_PackSnowData orig, MoreSlugcats.SnowSource snow_source) { 80 | if (snow_source.room is not Room room) return orig(snow_source); 81 | 82 | RoomCamera? room_camera = null; 83 | foreach (RoomCamera rc in room.world.game.cameras) { 84 | if (room == rc.room) { 85 | room_camera = rc; 86 | break; 87 | } 88 | } 89 | if (room_camera == null || room_camera.IsRoomBlacklisted(room.abstractRoom)) return orig(snow_source); 90 | 91 | // this should be more consistent with vanilla; min_camera_position is in most cases 92 | // the camera position of the bottom left screen (unless the max texture size is reached); 93 | // level texture size would be (1400f, 800f) for one screen; 94 | Vector2 min_camera_position = room.abstractRoom.GetFields().min_camera_position; 95 | 96 | // saves an approximation of a float (in [0, 1)) (in steps of size 1f/255f) and the remainder (times 255f for some reason) in a Vector2; 97 | Vector2 approximated_position_x = Custom.EncodeFloatRG((snow_source.pos.x - min_camera_position.x) / room_camera.levelTexture.width * 0.3f + 0.3f); 98 | Vector2 approximated_position_y = Custom.EncodeFloatRG((snow_source.pos.y - min_camera_position.y) / room_camera.levelTexture.height * 0.3f + 0.3f); 99 | 100 | // all snow sources being visible (prevents pop ins); for some reason there 101 | // is too much snow; this is a workaround such that snow sources have less 102 | // impact => less snow; 103 | Vector2 approximated_radius = Custom.EncodeFloatRG(snow_source.rad / (1600f * Mathf.Max(room_camera.levelTexture.width / 1400f, room_camera.levelTexture.height / 800f))); 104 | 105 | Vector4[] array = new Vector4[3]; 106 | array[0] = new Vector4(approximated_position_x.x, approximated_position_x.y, approximated_position_y.x, approximated_position_y.y); 107 | array[1] = new Vector4(approximated_radius.x, approximated_radius.y, snow_source.intensity, snow_source.noisiness); 108 | array[2] = new Vector4(0f, 0f, 0f, (float)snow_source.shape / 5f); 109 | return array; 110 | } 111 | 112 | private static void SnowSource_Update(On.MoreSlugcats.SnowSource.orig_Update orig, MoreSlugcats.SnowSource snow_source, bool eu) { 113 | // snowChange updates the overlay texture for fallen snow; 114 | if (snow_source.room?.game.cameras[0] is not RoomCamera room_camera) { 115 | orig(snow_source, eu); 116 | return; 117 | } 118 | 119 | if (room_camera.room is not Room room || room_camera.IsRoomBlacklisted(room.abstractRoom)) { 120 | orig(snow_source, eu); 121 | return; 122 | } 123 | 124 | // when entering the room; 125 | if (room_camera.snowChange) { 126 | snow_source.visibility = 1; 127 | return; 128 | } 129 | 130 | orig(snow_source, eu); 131 | 132 | // visibility equal to one means that it is used when the snow light is updated in roomCamera; 133 | // there doesn't seem to be downside to always setting it to one; 134 | // snowSource.visibility = roomCamera.CheckVisibility(snowSource); 135 | // snowSource.visibility = roomCamera.PositionVisibleInNextScreen(snowSource.pos, 100f, true) ? 1 : 0; 136 | snow_source.visibility = 1; 137 | 138 | // this is too performance intensive; 139 | // changing visibility seems to be enough anyways; 140 | // roomCamera.snowChange = true; 141 | 142 | // don't update again after the room is loaded; 143 | room_camera.snowChange = false; 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /SourceCode/AbstractRoomMod.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace SBCameraScroll; 3 | 4 | public static class AbstractRoomMod { 5 | // 6 | // parameters 7 | // 8 | 9 | public static readonly int maximum_texture_width = 16384; 10 | public static readonly int maximum_texture_height = 16384; 11 | public static bool HasCopyTextureSupport => SystemInfo.copyTextureSupport >= UnityEngine.Rendering.CopyTextureSupport.TextureToRT; 12 | 13 | // 14 | // variables 15 | // 16 | 17 | [Obsolete] 18 | internal static readonly Dictionary _all_attached_fields = new(); 19 | [Obsolete] 20 | public static Attached_Fields Get_Attached_Fields(this AbstractRoom abstract_room) => _all_attached_fields[abstract_room]; 21 | 22 | internal static readonly Dictionary _all_abstract_room_fields = new(); 23 | public static AbstractRoomFields GetFields(this AbstractRoom abstract_room) { 24 | _all_abstract_room_fields.TryGetValue(abstract_room, out AbstractRoomFields abstract_room_fields); 25 | return abstract_room_fields; 26 | } 27 | 28 | // 29 | // 30 | // 31 | 32 | internal static void OnEnable() { 33 | On.AbstractRoom.ctor += AbstractRoom_Ctor; 34 | On.AbstractRoom.Abstractize += AbstractRoom_Abstractize; 35 | } 36 | 37 | // ---------------- // 38 | // public functions // 39 | // ---------------- // 40 | 41 | public static RectInt? CalculateLevelTextureRectangle(string room_name) { 42 | Vector2[]? camera_positions = LoadCameraPositions(room_name); 43 | if (camera_positions == null) return null; 44 | 45 | CheckCameraPositions(ref camera_positions); 46 | if (camera_positions == null || camera_positions.Length == 0) { 47 | return null; 48 | } 49 | 50 | int total_width = 0; 51 | int total_height = 0; 52 | Vector2 min_camera_position = camera_positions[0]; 53 | 54 | foreach (Vector2 camera_position in camera_positions) { 55 | min_camera_position.x = Mathf.Min(min_camera_position.x, camera_position.x); 56 | min_camera_position.y = Mathf.Min(min_camera_position.y, camera_position.y); 57 | total_width = Mathf.Max(total_width, (int)camera_position.x + 1400); 58 | total_height = Mathf.Max(total_height, (int)camera_position.y + 800); 59 | } 60 | 61 | // Ignore the effect of any position modifiers here. 62 | total_width -= (int)min_camera_position.x; 63 | total_height -= (int)min_camera_position.y; 64 | 65 | return new RectInt((int)min_camera_position.x, (int)min_camera_position.y, total_width, total_height); 66 | } 67 | 68 | public static void CheckCameraPositions(ref Vector2[] camera_positions) { 69 | bool is_faulty_camera_found = false; 70 | foreach (Vector2 camera_position in camera_positions) { 71 | if (Mathf.Abs(camera_position.x) > 20000f || Mathf.Abs(camera_position.y) > 20000f) { 72 | is_faulty_camera_found = true; 73 | } 74 | } 75 | 76 | if (is_faulty_camera_found) { 77 | // SL_C01 has two cameras which are in outer space or something => needed too much memory 78 | Debug.Log($"{mod_id}: One or more camera screen positions are out of bounds. Remove them from cameraPositions."); 79 | 80 | List camera_positions_ = new(); 81 | foreach (Vector2 camera_position in camera_positions) { 82 | if (Mathf.Abs(camera_position.x) <= 20000f && Mathf.Abs(camera_position.y) <= 20000f) { 83 | camera_positions_.Add(camera_position); 84 | } 85 | } 86 | camera_positions = camera_positions_.ToArray(); 87 | } 88 | } 89 | 90 | public static void DestroyWormGrassInAbstractRoom(AbstractRoom abstract_room) { 91 | var abstract_room_fields = abstract_room.GetFields(); 92 | if (abstract_room_fields.worm_grass is WormGrass worm_grass) { 93 | Debug.Log($"{mod_id}: Remove worm grass from {abstract_room.name}."); 94 | 95 | // I expect only one wormGrass per room 96 | // wormGrass can have multiple patches with multiple tiles each 97 | 98 | worm_grass.Destroy(); 99 | WormGrassMod._all_worm_grass_fields.Remove(worm_grass); 100 | } 101 | abstract_room_fields.worm_grass = null; 102 | } 103 | 104 | public static Vector2[]? LoadCameraPositions(string? room_name) { 105 | if (room_name == null) return null; 106 | 107 | string file_path = WorldLoader.FindRoomFile(room_name, false, ".txt"); 108 | if (!File.Exists(file_path)) return null; 109 | 110 | // copy and paste from vanilla code 111 | string[] lines = File.ReadAllLines(file_path); 112 | int height = Convert.ToInt32(lines[1].Split('|')[0].Split('*')[1]); 113 | string[] line3 = lines[3].Split('|'); 114 | Vector2[] camera_positions = new Vector2[line3.Length]; 115 | 116 | for (int index = 0; index < line3.Length; ++index) { 117 | camera_positions[index] = new Vector2(Convert.ToSingle(line3[index].Split(',')[0]), height * 20f - 800f - Convert.ToSingle(line3[index].Split(',')[1])); 118 | } 119 | return camera_positions; 120 | } 121 | 122 | // I need to initialize the fields again if CRS changes the room name. 123 | // Otherwise, the camera textures are misaligned or not merged. 124 | public static void UpdateAttachedFields(AbstractRoom abstract_room) { 125 | var abstract_room_fields = abstract_room.GetFields(); 126 | 127 | // This changes the name if the REPLACEROOM feature is used. 128 | string room_name = abstract_room.FileName; 129 | if (room_name.Contains("OffScreenDen") || room_name.Contains("offscreenden")) { 130 | return; 131 | } 132 | 133 | if (CalculateLevelTextureRectangle(room_name) is not RectInt rect) { 134 | Debug.Log($"{mod_id}: Failed to initialize abstract_room_fields for room {room_name}."); 135 | return; 136 | } 137 | 138 | int total_width = rect.width; 139 | int total_height = rect.height; 140 | abstract_room_fields.min_camera_position = new Vector2(rect.x, rect.y); 141 | 142 | if (total_width > maximum_texture_width || total_height > maximum_texture_height) { 143 | Debug.Log($"{mod_id}: Warning! Merged texture width or height is too large. Setting to the maximum and hoping for the best."); 144 | total_width = Mathf.Min(total_width, maximum_texture_width); 145 | total_height = Mathf.Min(total_height, maximum_texture_height); 146 | } 147 | 148 | abstract_room_fields.total_width = total_width; 149 | abstract_room_fields.total_height = total_height; 150 | 151 | // Debug.Log($"{mod_id}: Initialized abstract_room_fields for room {room_name}."); 152 | } 153 | 154 | // 155 | // private 156 | // 157 | 158 | private static void AbstractRoom_Ctor(On.AbstractRoom.orig_ctor orig, AbstractRoom abstract_room, string room_name, int[] connections, int index, int swarm_room_index, int shelter_index, int gate_index) { 159 | orig(abstract_room, room_name, connections, index, swarm_room_index, shelter_index, gate_index); 160 | if (_all_abstract_room_fields.ContainsKey(abstract_room)) return; 161 | _all_abstract_room_fields.Add(abstract_room, new AbstractRoomFields()); 162 | UpdateAttachedFields(abstract_room); 163 | } 164 | 165 | private static void AbstractRoom_Abstractize(On.AbstractRoom.orig_Abstractize orig, AbstractRoom abstract_room) { 166 | DestroyWormGrassInAbstractRoom(abstract_room); 167 | orig(abstract_room); 168 | } 169 | 170 | // 171 | // 172 | // 173 | 174 | [Obsolete] 175 | public sealed class Attached_Fields { 176 | public int total_width = 1400; 177 | public int total_height = 800; 178 | 179 | public Vector2 min_camera_position = new(); 180 | public WormGrass? worm_grass = null; 181 | } 182 | 183 | public sealed class AbstractRoomFields { 184 | public int total_width = 1400; 185 | public int total_height = 800; 186 | 187 | public Vector2 min_camera_position = new(); 188 | public WormGrass? worm_grass = null; 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /SourceCode/WormGrassMod.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace SBCameraScroll; 3 | 4 | public static class WormGrassMod { 5 | // 6 | // variables 7 | // 8 | 9 | [Obsolete] 10 | internal static readonly Dictionary _all_attached_fields = new(); 11 | [Obsolete] 12 | public static Attached_Fields Get_Attached_Fields(this WormGrass worm_grass) => _all_attached_fields[worm_grass]; 13 | 14 | internal static readonly Dictionary _all_worm_grass_fields = new(); 15 | public static WormGrassFields GetFields(this WormGrass worm_grass) { 16 | _all_worm_grass_fields.TryGetValue(worm_grass, out WormGrassFields worm_grass_fields); 17 | return worm_grass_fields; 18 | } 19 | 20 | // 21 | // 22 | // 23 | 24 | internal static void OnEnable() { 25 | On.WormGrass.ctor += WormGrass_Ctor; // initialize variables 26 | On.WormGrass.Explosion += WormGrass_Explosion; 27 | // On.WormGrass.NewCameraPos += WormGrass_NewCameraPos; // skip // leave cosmeticWorms empty 28 | On.WormGrass.Update += WormGrass_Update; 29 | } 30 | 31 | // ---------------- // 32 | // public functions // 33 | // ---------------- // 34 | 35 | public static void UpdatePatchTile(WormGrassFields worm_grass_fields, WormGrass.WormGrassPatch worm_grass_patch, Room worm_grass_room, int tile_index) { 36 | System.Random random = new System.Random(); 37 | 38 | // in the hunter cutscene this function is called before rainworldgame.ctor 39 | ref List cosmetic_worms_on_tile = ref worm_grass_fields.cosmetic_worms_on_tiles[worm_grass_patch][tile_index]; 40 | if (cosmetic_worms_on_tile.Count == 0 && worm_grass_patch.cosmeticWormPositions[tile_index].Length > 0 && worm_grass_room.ViewedByAnyCamera(worm_grass_room.MiddleOfTile(worm_grass_patch.tiles[tile_index]), margin: 200f)) { 41 | for (int worm_index = 0; worm_index < worm_grass_patch.cosmeticWormPositions[tile_index].Length; ++worm_index) { 42 | WormGrass.Worm worm = new WormGrass.Worm( 43 | worm_grass_patch.wormGrass, 44 | worm_grass_patch, 45 | worm_grass_patch.cosmeticWormPositions[tile_index][worm_index], 46 | worm_grass_patch.cosmeticWormLengths[tile_index][worm_index, 0], 47 | worm_grass_patch.sizes[tile_index, 1], 48 | worm_grass_patch.cosmeticWormLengths[tile_index][worm_index, 1], 49 | true, 50 | random 51 | ); 52 | 53 | cosmetic_worms_on_tile.Add(worm); 54 | worm_grass_patch.wormGrass.room.AddObject(worm); 55 | } 56 | } else if (cosmetic_worms_on_tile.Count > 0 && !worm_grass_room.ViewedByAnyCamera(worm_grass_room.MiddleOfTile(worm_grass_patch.tiles[tile_index]), margin: 600f)) { 57 | foreach (WormGrass.Worm worm in cosmetic_worms_on_tile) { 58 | worm.Destroy(); // should be removed automatically from room 59 | } 60 | cosmetic_worms_on_tile.Clear(); 61 | } 62 | } 63 | 64 | // ----------------- // 65 | // private functions // 66 | // ----------------- // 67 | 68 | private static void WormGrass_Ctor(On.WormGrass.orig_ctor orig, WormGrass worm_grass, Room room, List tiles) { 69 | if (!_all_worm_grass_fields.ContainsKey(worm_grass)) { 70 | _all_worm_grass_fields.Add(worm_grass, new WormGrassFields()); 71 | } 72 | orig(worm_grass, room, tiles); // needs attachedFields for wormGrass 73 | 74 | if (worm_grass.patches.Count == 0) { 75 | Debug.Log($"{mod_id}: This worm grass for room {room.abstractRoom.name} has no patches. Destroy."); 76 | worm_grass.Destroy(); 77 | _all_worm_grass_fields.Remove(worm_grass); 78 | } else { 79 | var abstract_room_fields = room.abstractRoom.GetFields(); 80 | if (abstract_room_fields.worm_grass is WormGrass worm_grass_) { 81 | Debug.Log($"{mod_id}: There is already worm grass in {room.abstractRoom.name}. Destroy the old one."); 82 | worm_grass_.Destroy(); 83 | _all_worm_grass_fields.Remove(worm_grass_); 84 | } 85 | abstract_room_fields.worm_grass = worm_grass; 86 | } 87 | } 88 | 89 | private static void WormGrass_Explosion(On.WormGrass.orig_Explosion orig, WormGrass worm_grass, Explosion explosion) { 90 | orig(worm_grass, explosion); // takes care of small worms // cosmeticWorms is empty because NewCameraPos() is skipped 91 | 92 | if (worm_grass.slatedForDeletetion) return; 93 | 94 | var worm_grass_fields = worm_grass.GetFields(); 95 | foreach (List[] cosmetic_worms_on_tiles in worm_grass_fields.cosmetic_worms_on_tiles.Values) { 96 | foreach (List cosmetic_worms_on_tile in cosmetic_worms_on_tiles) { 97 | foreach (WormGrass.Worm worm in cosmetic_worms_on_tile) // loaded worms 98 | { 99 | // vanilla copy & paste 100 | if (Custom.DistLess(worm.pos, explosion.pos, explosion.rad * 2f)) { 101 | float distance = Mathf.InverseLerp(explosion.rad * 2f, explosion.rad, Vector2.Distance(worm.pos, explosion.pos)); // between 0 and 1 102 | if (UnityEngine.Random.value < distance) { 103 | worm.vel += Custom.DirVec(explosion.pos, worm.pos) * explosion.force * 2f * distance; 104 | worm.excitement = 0.0f; 105 | worm.focusCreature = null; 106 | worm.dragForce = 0.0f; 107 | worm.attachedChunk = null; 108 | } 109 | } 110 | } 111 | } 112 | } 113 | } 114 | 115 | // not need atm because of WormGrass_Update() 116 | // private static void WormGrass_NewCameraPos(On.WormGrass.orig_NewCameraPos orig, UpdatableAndDeletable updatableAndDeletable) 117 | // { 118 | // // don't add Worms to WormGrass.cosmeticWorms // save performance 119 | // // use cosmeticWormsOnTiles instead 120 | // return; 121 | // } 122 | 123 | private static void WormGrass_Update(On.WormGrass.orig_Update orig, UpdatableAndDeletable updatable_and_deletable, bool eu) { 124 | // I could also call orig() instead // this way NewCameraPos() and related things are skipped 125 | // currently that can pose problems with SplitScreenMod 126 | // because WormGrass.cameraPositions might not be initialized for camera two 127 | 128 | WormGrass worm_grass = (WormGrass)updatable_and_deletable; 129 | worm_grass.evenUpdate = eu; 130 | 131 | foreach (WormGrass.WormGrassPatch worm_grass_patch in worm_grass.patches) { 132 | worm_grass_patch.Update(); 133 | } 134 | 135 | if (worm_grass.slatedForDeletetion) return; 136 | 137 | // different update logic for worms 138 | // update each tile based on distance 139 | // not when currentCameraIndex changes 140 | 141 | var worm_grass_fields = worm_grass.GetFields(); 142 | foreach (WormGrass.WormGrassPatch worm_grass_patch in worm_grass_fields.cosmetic_worms_on_tiles.Keys) { 143 | for (int tile_index = 0; tile_index < worm_grass_patch.tiles.Count; ++tile_index) { // update all tiles at once 144 | UpdatePatchTile(worm_grass_fields, worm_grass_patch, worm_grass.room, tile_index); 145 | } 146 | } 147 | } 148 | 149 | // 150 | // 151 | // 152 | 153 | [Obsolete] 154 | public sealed class Attached_Fields { 155 | // the difference between this and WormGrass.cosmeticWorms is that I can update them tile by tile 156 | // vanilla looks at every worm but only when switching screens // costs too much performance otherwise 157 | public Dictionary[]> cosmetic_worms_on_tiles = new(); 158 | } 159 | 160 | public sealed class WormGrassFields { 161 | // the difference between this and WormGrass.cosmeticWorms is that I can update them tile by tile 162 | // vanilla looks at every worm but only when switching screens // costs too much performance otherwise 163 | public Dictionary[]> cosmetic_worms_on_tiles = new(); 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /ModdedShaders/Decal.shader: -------------------------------------------------------------------------------- 1 | // Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)' 2 | 3 | 4 | // Upgrade NOTE: replaced 'samplerRECT' with 'sampler2D' 5 | 6 | //from http://forum.unity3d.com/threads/68402-Making-a-2D-game-for-iPhone-iPad-and-need-better-performance 7 | 8 | Shader "SBCameraScroll/Decal" //Unlit Transparent Vertex Colored Additive 9 | { 10 | Properties 11 | { 12 | _MainTex ("Base (RGB) Trans (A)", 2D) = "white" {} 13 | } 14 | 15 | Category 16 | { 17 | Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"} 18 | ZWrite Off 19 | //Alphatest Greater 0 20 | Blend SrcAlpha OneMinusSrcAlpha 21 | Fog { Color(0,0,0,0) } 22 | Lighting Off 23 | Cull Off //we can turn backface culling off because we know nothing will be facing backwards 24 | 25 | BindChannels 26 | { 27 | Bind "Vertex", vertex 28 | Bind "texcoord", texcoord 29 | Bind "Color", color 30 | } 31 | 32 | SubShader 33 | { 34 | Pass 35 | { 36 | //SetTexture [_MainTex] 37 | //{ 38 | // Combine texture * primary 39 | //} 40 | 41 | 42 | 43 | CGPROGRAM 44 | #pragma target 3.0 45 | #pragma vertex vert 46 | #pragma fragment frag 47 | #include "UnityCG.cginc" 48 | #include "_ShaderFix.cginc" 49 | #include "_UrbanLife.cginc" 50 | 51 | //#pragma profileoption NumTemps=64 52 | //#pragma profileoption NumInstructionSlots=2048 53 | 54 | 55 | uniform float _palette; 56 | uniform float _RAIN; 57 | uniform float _light = 0; 58 | uniform float4 _spriteRect; 59 | 60 | uniform float4 _lightDirAndPixelSize; 61 | uniform float _fogAmount; 62 | uniform float _waterLevel; 63 | uniform float _Grime; 64 | uniform float _SwarmRoom; 65 | uniform float _WetTerrain; 66 | uniform float _cloudsSpeed; 67 | 68 | #if defined(SHADER_API_PSSL) 69 | sampler2D _GrabTexture; 70 | #else 71 | sampler2D _GrabTexture : register(s0); 72 | #endif 73 | sampler2D _MainTex; 74 | sampler2D _NoiseTex; 75 | sampler2D _NoiseTex2; 76 | sampler2D _LevelTex; 77 | sampler2D _PalTex; 78 | 79 | uniform float2 _screenSize; 80 | 81 | sampler2D _DynamicLevelElements; 82 | sampler2D _OrigLevelTex; 83 | #include "_RippleClip.cginc" 84 | #include "_DynamicLevelClip.cginc" 85 | #include "_Functions.cginc" 86 | 87 | struct v2f { 88 | float4 pos : SV_POSITION; 89 | float2 uv : TEXCOORD0; 90 | float2 scrPos : TEXCOORD1; 91 | float4 clr : COLOR; 92 | }; 93 | 94 | float4 _MainTex_ST; 95 | 96 | v2f vert (appdata_full v) 97 | { 98 | v2f o; 99 | o.pos = UnityObjectToClipPos (v.vertex); 100 | o.uv = TRANSFORM_TEX (v.texcoord, _MainTex); 101 | o.scrPos = ComputeScreenPos(o.pos); 102 | o.clr = v.color; 103 | return o; 104 | } 105 | 106 | 107 | half3 Multiply(half3 cA, half3 cB){ 108 | return half3(cA.x*cB.x, cA.y*cB.y, cA.z*cB.z); 109 | } 110 | half3 Screen(half3 cA, half3 cB){ 111 | return half3(1.0-(1.0-cA.x)*(1.0-cB.x), 1.0-(1.0-cA.y)*(1.0-cB.y), 1.0-(1.0-cA.z)*(1.0-cB.z)); 112 | } 113 | half3 Overlay(half3 cA, half3 cB){ 114 | return half3( 115 | cB.x <= 0.5 ? cA.x*cB.x : 1.0-(1.0-cA.x)*(1.0-cB.x), 116 | cB.y <= 0.5 ? cA.y*cB.y : 1.0-(1.0-cA.y)*(1.0-cB.y), 117 | cB.z <= 0.5 ? cA.z*cB.z : 1.0-(1.0-cA.z)*(1.0-cB.z)); 118 | } 119 | 120 | half4 frag (v2f i) : SV_Target 121 | { 122 | 123 | float2 textCoord = float2(floor(i.scrPos.x*_screenSize.x)/_screenSize.x, floor(i.scrPos.y*_screenSize.y)/_screenSize.y); 124 | 125 | 126 | textCoord.x -= _spriteRect.x; 127 | textCoord.y -= _spriteRect.y; 128 | 129 | textCoord.x /= _spriteRect.z - _spriteRect.x; 130 | textCoord.y /= _spriteRect.w - _spriteRect.y; 131 | 132 | dynamicLevelClip(i.scrPos, textCoord); 133 | 134 | float ugh = fmod(fmod( round(tex2D(_MainTex, float2(textCoord.x, textCoord.y)).x*255) , 90)-1, 30)/300.0; 135 | float displace = tex2D(_NoiseTex, float2((textCoord.x * 1.5) - ugh + (_RAIN*0.01), (textCoord.y*0.25) - ugh + _RAIN * 0.05) ).x; 136 | displace = clamp((sin((displace + textCoord.x + textCoord.y + _RAIN*0.1) * 3 * 3.14)-0.95)*20, 0, 1); 137 | 138 | 139 | 140 | //half2 screenPos = half2(lerp(_spriteRect.x, _spriteRect.z, textCoord.x), lerp(_spriteRect.y, _spriteRect.w, textCoord.y)); 141 | 142 | if (_WetTerrain < 0.5 || 1-i.scrPos.y > _waterLevel) displace = 0; 143 | 144 | half4 texcol = tex2D(_LevelTex, float2(textCoord.x, textCoord.y+displace*0.001)); 145 | 146 | 147 | if (texcol.x == 1.0 && texcol.y == 1.0 && texcol.z == 1.0){ 148 | return half4(0,0,0,0); 149 | }else{ 150 | int red = round(texcol.x * 255); 151 | 152 | // vanilla: 153 | // half shadow = tex2D(_NoiseTex, float2((textCoord.x*0.5) + (_RAIN*0.1*_cloudsSpeed) - (0.003*fmod(red, 30.0)), 1-(textCoord.y*0.5) + (_RAIN*0.2*_cloudsSpeed) - (0.003*fmod(red, 30.0)))).x; 154 | // shadow = 0.5 + sin(fmod(shadow+(_RAIN*0.1*_cloudsSpeed)-textCoord.y, 1)*3.14*2)*0.5; 155 | 156 | // modded: 157 | // The clouds light mask can be stretched. The mask is applied to the full 158 | // level texture. Merged level textures contain multiple screens. 159 | float number_of_screens_x = _spriteRect.z - _spriteRect.x; 160 | float number_of_screens_y = _spriteRect.w - _spriteRect.y; 161 | half shadow = tex2D(_NoiseTex, float2((number_of_screens_x*textCoord.x*0.5) + (_RAIN*0.1*_cloudsSpeed) - (0.003*fmod(red, 30.0)), 1-(number_of_screens_y*textCoord.y*0.5) + (_RAIN*0.2*_cloudsSpeed) - (0.003*fmod(red, 30.0)))).x; 162 | shadow = 0.5 + sin(fmod(shadow+(_RAIN*0.1*_cloudsSpeed)-number_of_screens_y*textCoord.y, 1)*3.14*2)*0.5; 163 | 164 | // vanilla: 165 | shadow = clamp(((shadow - 0.5)*6)+0.5-(_light*4), 0,1); 166 | 167 | if (red > 90) red -= 90; 168 | else shadow = 1.0; 169 | 170 | 171 | int paletteColor = clamp(floor((red-1)/30.0), 0, 2);//some distant objects want to get palette color 3, so we clamp it 172 | 173 | 174 | red = fmod(red-1, 30.0); 175 | 176 | #if URBANLIFE 177 | shadow = max(UrbanLifeShadows(i.scrPos,red),shadow); 178 | #endif 179 | if(red / 30.0 < i.clr.x || red / 30.0 > i.clr.y) return half4(0,0,0,0); 180 | 181 | 182 | if (shadow != 1 && red >= 5) { 183 | float4 shadowFix =FixEdgeShadowStretch(i.scrPos,true); 184 | half2 grabPos = float2(i.scrPos.x + -_lightDirAndPixelSize.x*_lightDirAndPixelSize.z*(red-5)*shadowFix.x, 185 | i.scrPos.y + _lightDirAndPixelSize.y*_lightDirAndPixelSize.w*(red-5)*shadowFix.y); 186 | grabPos = lerp(grabPos,((grabPos-half2(0.5, 0.3))*(1 + (red-5.0)/460.0))+half2(0.5, 0.3),shadowFix.zw); 187 | float4 grabTexCol2 = tex2D(_GrabTexture, grabPos); 188 | if (grabTexCol2.x != 0.0 || grabTexCol2.y != 0.0 || grabTexCol2.z != 0.0){ 189 | shadow = 1; 190 | } 191 | } 192 | 193 | half4 terrainCol = lerp(tex2D(_PalTex, float2((red)/32.0, (paletteColor + 3 + 0.5)/8.0)), tex2D(_PalTex, float2((red)/32.0, (paletteColor + 0.5)/8.0)), shadow); 194 | 195 | terrainCol = lerp(terrainCol, tex2D(_PalTex, float2(1.5/32.0, 7.5/8.0)), clamp(red*_fogAmount/30.0, 0, 1)); 196 | 197 | 198 | 199 | if (red >= 5){ 200 | float4 grabTexCol = tex2D(_GrabTexture, float2(i.scrPos.x, i.scrPos.y)); 201 | if (grabTexCol.x > 1.0/255.0 || grabTexCol.y != 0.0 || grabTexCol.z != 0.0) 202 | return half4(0,0,0,0); 203 | } 204 | 205 | half2 grabPos = i.uv; 206 | grabPos -= half2(0.5, 0.5); 207 | grabPos *= lerp(1, 1.5, red/30.0); 208 | grabPos += half2(0.5, 0.5); 209 | 210 | half h = textCoord.x; 211 | h = lerp(floor(h*700.0)/700.0, h, 0.5); 212 | 213 | grabPos.y += pow(max(0,tex2D(_NoiseTex2, half2(h*4, textCoord.y*0.01 + red/100.0)).x-0.5)*2, 3-i.clr.z)*0.3*i.clr.z; 214 | 215 | half4 myCol = tex2D(_MainTex, grabPos); 216 | half3 lightCol = lerp(terrainCol.xyz, myCol.xyz, 0.5); 217 | lightCol = lerp(lightCol, Screen(terrainCol.xyz, myCol.xyz), (1.0-shadow)*(paletteColor == 2 ? 0.9 : 0.25)); 218 | half4 result = half4(lerp(lightCol, Multiply(terrainCol.xyz, myCol.xyz), lerp(0.2, 0.3, shadow)), myCol.w*lerp(1, 0.25+0.25*i.clr.w, shadow)*i.clr.w); 219 | smoothRippleClip(result, i.scrPos); 220 | return result; 221 | // return half4(lerp(Overlay(terrainCol.xyz, myCol.xyz), Multiply(terrainCol.xyz, myCol.xyz), lerp(0.2, 0.8, shadow)), myCol.w*lerp(1, 0.3, shadow)*i.clr.w); 222 | 223 | // return half4(lerp(lerp(terrainCol.xyz, myCol.xyz, 0.5), lerp(Screen(terrainCol.xyz, myCol.xyz), Multiply(terrainCol.xyz, myCol.xyz), lerp(0.2, 0.8, shadow)), 0.5), myCol.w*lerp(1, 0.3, shadow)*i.clr.w); 224 | //return half4(Overlay(terrainCol.xyz, myCol.xyz), myCol.w*i.clr.w); 225 | 226 | } 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | } 237 | ENDCG 238 | 239 | 240 | 241 | } 242 | } 243 | } 244 | } 245 | -------------------------------------------------------------------------------- /ModdedShaders/DisplaySnowShader.shader: -------------------------------------------------------------------------------- 1 | Shader "SBCameraScroll/DisplaySnowShader" 2 | { 3 | Properties 4 | { 5 | _MainTex ("Texture", 2D) = "white" {} 6 | } 7 | SubShader 8 | { 9 | Tags {"Queue"="AlphaTest" "IgnoreProjector"="True" "RenderType"="TransparentCutout"} 10 | ZWrite Off 11 | //AlphaTest Greater 0.8 12 | Blend SrcAlpha OneMinusSrcAlpha 13 | Fog { Color(0,0,0,0) } 14 | Lighting Off 15 | Cull Off 16 | BindChannels 17 | { 18 | Bind "Vertex", vertex 19 | Bind "texcoord", texcoord 20 | Bind "Color", color 21 | } 22 | Pass 23 | { 24 | 25 | AlphaTest Greater 0.8 26 | CGPROGRAM 27 | #pragma target 4.0 28 | #pragma vertex vert 29 | #pragma fragment frag 30 | #pragma profileoption NumInstructionSlots=4096 31 | #pragma profileoption NumMathInstructionSlots=4096 32 | #pragma multi_compile __ HR 33 | #pragma exclude_renderers OpenGL 34 | #include "UnityCG.cginc" 35 | #include "_ShaderFix.cginc" 36 | #include "_Functions.cginc" 37 | #include "_RippleClip.cginc" 38 | #include "_Snow.cginc" 39 | 40 | 41 | 42 | 43 | struct appdata 44 | { 45 | float4 vertex : POSITION; 46 | float2 uv : TEXCOORD0; 47 | }; 48 | 49 | struct v2f 50 | { 51 | float2 uv : TEXCOORD0; 52 | float4 pos : SV_POSITION; 53 | float4 scrPos : TEXCOORD1; 54 | float4 clr : COLOR; 55 | }; 56 | #if defined(SHADER_API_PSSL) 57 | sampler2D _GrabTexture; 58 | #else 59 | sampler2D _GrabTexture : register(s0); 60 | #endif 61 | sampler2D _PalTex; 62 | float _light = 0; 63 | sampler2D _MainTex; 64 | float2 _MainTex_TexelSize; 65 | float4 _MainTex_ST; 66 | sampler2D _LevelTex; 67 | float2 _LevelTex_TexelSize; 68 | float4 _lightDirAndPixelSize; 69 | float2 _screenSize; 70 | float4 _spriteRect; 71 | float4 _EffectColor; 72 | float _WetTerrain; 73 | float _waterLevel; 74 | float _RAIN; 75 | float _cloudsSpeed; 76 | float _fogAmount; 77 | sampler2D _NoiseTex; 78 | sampler2D _NoiseTex2; 79 | sampler2D _SnowSources; 80 | float2 _SnowSources_TexelSize; 81 | 82 | 83 | v2f vert (appdata_full v) 84 | { 85 | v2f o; 86 | o.pos = UnityObjectToClipPos (v.vertex); 87 | o.uv = TRANSFORM_TEX(v.texcoord, _MainTex); 88 | o.clr = v.color; 89 | o.scrPos = ComputeScreenPos(o.pos); 90 | return o; 91 | } 92 | 93 | float GetDepth (float a) 94 | { 95 | if (a==1.0) return 255; 96 | a=round(a*255); 97 | float shadows = (step(a,90)*-1+1)*90; 98 | return fmod(a-shadows-1, 30); 99 | } 100 | 101 | float GetShadows (float a) 102 | { 103 | // if (a==1.0) return 1; 104 | a*=255; 105 | return (step(round(a),90)*-1+1); 106 | } 107 | float cubicPulse( float c, float w, float x ) 108 | { 109 | x = abs(x - c); 110 | if( x>w ) return 0.0; 111 | x /= w; 112 | return 1.0 - x*x*(3.0-2.0*x); 113 | } 114 | float easeInExpo(float t) { 115 | return (t == 0.0) ? 0.0 : pow(2.0, 10.0 * (t - 1.0)); 116 | } 117 | float easeOutExpo(float t) { 118 | return (t == 1.0) ? 1.0 : -pow(2.0, -10.0 * t) + 1.0; 119 | } 120 | float easeOutCubic(float t) { 121 | return (t = t - 1.0) * t * t + 1.0; 122 | } 123 | float impulse( float k, float x ) 124 | { 125 | const float h = k*x; 126 | return h*exp(1.0-h); 127 | } 128 | float easeOutQuad(float t) { 129 | return -1.0 * t * (t - 2.0); 130 | } 131 | float easeInQuad(float t) { 132 | return t * t; 133 | } 134 | float easeInOutExpo(float t) { 135 | if (t == 0.0 || t == 1.0) { 136 | return t; 137 | } 138 | if ((t *= 2.0) < 1.0) { 139 | return 0.5 * pow(2.0, 10.0 * (t - 1.0)); 140 | } else { 141 | return 0.5 * (-pow(2.0, -10.0 * (t - 1.0)) + 2.0); 142 | } 143 | } 144 | float4 frag(v2f i) : SV_Target 145 | { 146 | 147 | /////////////////////////////////////////////////////// 148 | // sample the texture 149 | float2 textCoord = float2(floor(i.scrPos.x*_screenSize.x)/_screenSize.x, floor(i.scrPos.y*_screenSize.y)/_screenSize.y); 150 | textCoord.x -= _spriteRect.x; 151 | textCoord.y -= _spriteRect.y; 152 | 153 | textCoord.x /= _spriteRect.z - _spriteRect.x; 154 | textCoord.y /= _spriteRect.w - _spriteRect.y; 155 | #if RIPPLE 156 | fixed rippleMask = tex2D(_GameplayRippleMask,i.scrPos).x; 157 | textCoord += rippleDistortion(rippleMask, i.scrPos)*1; 158 | #endif 159 | float2 distUV=float2(textCoord.x,textCoord.y); 160 | float4 texCol = tex2D(_SnowTex,distUV); 161 | float depth = GetDepth(texCol.x); 162 | // return (float4)depth/30+float4(0,0,0,1); 163 | float4 grabColor = tex2D(_GrabTexture, float2(i.scrPos.x, i.scrPos.y)); 164 | clip(grabColor.a - 0.8); 165 | if( (grabColor.x > 1.0/255.0 || grabColor.y != 0.0 || grabColor.z != 0.0)&&depth>5.0) 166 | return float4(0,0,0,0); 167 | float shadows = GetShadows(texCol.x); 168 | float shadowGradient = (texCol.x-(depth+1)/255-(shadows/255*90))*4.25; 169 | // float shadowGradient = texCol.x-(shadows/255*90); 170 | float4 fog = tex2D(_PalTex, float2(1.5/32.0, 7.5/8.0)); 171 | // float4 snow = (float4)(depth/30+shadows*.4); 172 | 173 | 174 | //////////////RAINWORLD SHADOWS 175 | // vanilla: 176 | // float shadow = tex2D(_NoiseTex, float2((textCoord.x*0.5) + (_RAIN*0.1*_cloudsSpeed) - (0.003*(clamp(depth,0,30))), 1-(textCoord.y*0.5) + (_RAIN*0.2*_cloudsSpeed) - (0.003*(clamp(depth,0,30))))).x; 177 | // shadow = 0.5 + sin(fmod(shadow+(_RAIN*0.1*_cloudsSpeed)-textCoord.y, 1)*3.14*2)*0.5; 178 | 179 | // modded: 180 | // The clouds light mask can be stretched. The mask is 181 | // applied to the full level texture. Merged level 182 | // textures contain multiple screens. 183 | float number_of_screens_x = _spriteRect.z - _spriteRect.x; 184 | float number_of_screens_y = _spriteRect.w - _spriteRect.y; 185 | float shadow = tex2D(_NoiseTex, float2((number_of_screens_x*textCoord.x*0.5) + (_RAIN*0.1*_cloudsSpeed) - (0.003*(clamp(depth,0,30))), 1-(number_of_screens_y*textCoord.y*0.5) + (_RAIN*0.2*_cloudsSpeed) - (0.003*(clamp(depth,0,30))))).x; 186 | shadow = 0.5 + sin(fmod(shadow+(_RAIN*0.1*_cloudsSpeed)-number_of_screens_y*textCoord.y, 1)*3.14*2)*0.5; 187 | 188 | // vanilla: 189 | shadow = clamp(((shadow - 0.5)*6)+0.5-(_light*4), 0,1); 190 | 191 | float4 shadowFix =FixEdgeShadowStretch(i.scrPos,true); 192 | float2 grabPos = float2(i.scrPos.x + -_lightDirAndPixelSize.x*_lightDirAndPixelSize.z*(depth-5)*shadowFix.x, 193 | i.scrPos.y + _lightDirAndPixelSize.y*_lightDirAndPixelSize.w*(depth-5)*shadowFix.y); 194 | grabPos = lerp(grabPos, ((grabPos-float2(0.5, 0.3))*(1 + (depth-5.0)/460.0))+float2(0.5, 0.3), shadowFix.zw); 195 | float4 grabColor2 = tex2D(_GrabTexture,grabPos); 196 | grabColor2 = -step(grabColor2,0.003921568627451)+1; 197 | if (depth < 6) { 198 | grabColor2 = 0; 199 | } 200 | //////////////CONTINUE 201 | #if HR 202 | float4 snow = tex2D(_PalTex,float2(depth*0.03333*0.6375,0.125+(shadowGradient*0.0625))); 203 | float4 snowLight = tex2D(_PalTex,float2(depth*0.03333*0.6375,0.57+(shadowGradient*0.0625))); 204 | snow = lerp(snow,snowLight,(-shadow+1)*shadows*(-grabColor2+1)); 205 | snow = .02+snow-shadowGradient*.01; 206 | snow = lerp(snow,snow+fog*.2,_fogAmount*(depth/30)); 207 | 208 | 209 | 210 | // if (texCol.y ==1.0) 211 | // return float4(0,0,0,0); 212 | // return depth/30; 213 | float4 result = float4(snow.xyz,texCol.y); 214 | smoothRippleClip(result, i.scrPos); 215 | return result; 216 | #else 217 | float4 snow = tex2D(_PalTex,float2(depth*0.03333*0.9375,0.125+(shadowGradient*0.0625))); 218 | float4 snowLight = tex2D(_PalTex,float2(depth*0.03333*0.9375,0.57+(shadowGradient*0.0625))); 219 | 220 | snow = lerp(snow,snowLight,(-shadow+1)*shadows*(-grabColor2+1)); 221 | snow+=.2+shadowGradient*.1; 222 | snow = lerp(snow,fog,_fogAmount*(depth/30)); 223 | // if (texCol.y ==1.0) 224 | // return float4(0,0,0,0); 225 | // return depth/30; 226 | float4 result = float4(snow.xyz,texCol.y); 227 | smoothRippleClip(result, i.scrPos); 228 | return result; 229 | #endif 230 | } 231 | ENDCG 232 | } 233 | } 234 | FallBack "Transparent" 235 | } 236 | -------------------------------------------------------------------------------- /ModdedShaders/DeepWater.shader: -------------------------------------------------------------------------------- 1 | // Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)' 2 | 3 | // Upgrade NOTE: replaced 'samplerRECT' with 'sampler2D' 4 | 5 | //from http://forum.unity3d.com/threads/68402-Making-a-2D-game-for-iPhone-iPad-and-need-better-performance 6 | Shader "SBCameraScroll/DeepWater" 7 | { 8 | Properties 9 | { 10 | _MainTex ("Base (RGB) Trans (A)", 2D) = "white" {} 11 | } 12 | 13 | Category 14 | { 15 | Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"} 16 | ZWrite Off 17 | //Alphatest Greater 0 18 | Blend SrcAlpha OneMinusSrcAlpha 19 | Fog { Color(0,0,0,0) } 20 | Lighting Off 21 | Cull Off //we can turn backface culling off because we know nothing will be facing backwards 22 | 23 | BindChannels 24 | { 25 | Bind "Vertex", vertex 26 | Bind "texcoord", texcoord 27 | Bind "Color", color 28 | } 29 | 30 | SubShader 31 | { 32 | Pass 33 | { 34 | //SetTexture [_MainTex] 35 | //{ 36 | // Combine texture * primary 37 | //} 38 | 39 | 40 | 41 | CGPROGRAM 42 | #pragma target 3.0 43 | #pragma vertex vert 44 | #pragma fragment frag 45 | #pragma exclude_renderers OpenGL 46 | #include "UnityCG.cginc" 47 | #include "_ShaderFix.cginc" 48 | #include "_TerrainMask.cginc" 49 | #include "_RippleClip.cginc" 50 | #include "_BrainMoldClip.cginc" 51 | 52 | #pragma multi_compile _ Gutter 53 | #define MAX_AIR_POCKETS 8 54 | 55 | sampler2D _MainTex; 56 | sampler2D _LevelTex; 57 | sampler2D _NoiseTex; 58 | sampler2D _PalTex; 59 | #if defined(SHADER_API_PSSL) 60 | sampler2D _GrabTexture; 61 | #else 62 | sampler2D _GrabTexture : register(s0); 63 | #endif 64 | 65 | uniform float _RAIN; 66 | uniform float4 _spriteRect; 67 | uniform float2 _screenSize; 68 | uniform float _waterDepth; 69 | float _waterLevel; 70 | float4 _airPockets[MAX_AIR_POCKETS]; 71 | 72 | 73 | 74 | struct v2f { 75 | float4 pos : SV_POSITION; 76 | float2 uv : TEXCOORD0; 77 | float2 scrPos : TEXCOORD1; 78 | float4 clr : COLOR; 79 | #if Gutter 80 | float2 data[4] : TEXCOORD2; 81 | #endif 82 | }; 83 | 84 | float4 _MainTex_ST; 85 | float pulse(float pos, float width, float x){x = smoothstep(1.-width,1.,1.-abs((x-pos)));return x;} 86 | v2f vert (appdata_full v) 87 | { 88 | v2f o; 89 | o.pos = UnityObjectToClipPos (v.vertex); 90 | o.uv = TRANSFORM_TEX (v.texcoord, _MainTex); 91 | o.scrPos = ComputeScreenPos(o.pos); 92 | o.clr = v.color; 93 | #if Gutter 94 | float2 textCoord = float2(floor(o.scrPos.x*_screenSize.x)/_screenSize.x, floor(o.scrPos.y*_screenSize.y)/_screenSize.y); 95 | 96 | textCoord.x -= _spriteRect.x; 97 | textCoord.y -= _spriteRect.y; 98 | 99 | textCoord.x /= _spriteRect.z - _spriteRect.x; 100 | textCoord.y /= _spriteRect.w - _spriteRect.y; 101 | textCoord*=fixed2(6,5); 102 | o.data[0] = textCoord+float2(0,_RAIN*.5); 103 | o.data[1] = -textCoord*float2(1.5,1.5)+float2(0,_RAIN*.25); 104 | o.data[2] = textCoord*.25+float2(_RAIN*.25,0); 105 | o.data[3] = -textCoord*.2+float2(_RAIN*.25,0); 106 | #endif 107 | return o; 108 | } 109 | float ShEaseOutCubic(float t) { 110 | return (t = t - 1.0) * t * t + 1.0; 111 | } 112 | 113 | half4 frag (v2f i) : SV_Target 114 | { 115 | // Cut out around air pockets 116 | if (i.clr.a == 1) { 117 | for (int j = 0; j < MAX_AIR_POCKETS && _airPockets[j].z > _airPockets[j].x; j++) { 118 | float4 bounds = _airPockets[j]; 119 | if (all(i.scrPos > bounds.xy) && all(i.scrPos < bounds.zw)) 120 | discard; 121 | } 122 | } 123 | 124 | float2 textCoord = float2(floor(i.scrPos.x*_screenSize.x)/_screenSize.x, floor(i.scrPos.y*_screenSize.y)/_screenSize.y); 125 | 126 | textCoord.x -= _spriteRect.x; 127 | textCoord.y -= _spriteRect.y; 128 | 129 | textCoord.x /= _spriteRect.z - _spriteRect.x; 130 | textCoord.y /= _spriteRect.w - _spriteRect.y; 131 | 132 | half rbcol = (sin((_RAIN + (tex2D(_NoiseTex, float2(textCoord.x*1.2, textCoord.y*1.2) ).x * 3) + 0/12.0) * 3.14 * 2)*0.5)+0.5; 133 | 134 | // vanilla: 135 | // float2 distortion = float2(lerp(-0.002, 0.002, rbcol)*lerp(1, 20, pow(i.uv.x, 200)), -0.02 * pow(i.uv.x, 8)); 136 | 137 | // modded: 138 | // reduces the magnitude of the distortion effect; this can get out of hand 139 | // in larger rooms otherwise; 140 | // I probably can just use _LevelTex_TexelSize instead of 1/level_texture_size. 141 | float2 level_texture_size = float2((_spriteRect.z - _spriteRect.x) * _screenSize.x, (_spriteRect.w - _spriteRect.y) * _screenSize.y); 142 | float2 distortion = float2(lerp(-0.002, 0.002, rbcol) * lerp(1, 20, pow(i.uv.y, 200)) * 1400 / level_texture_size.x, -0.02 * pow(i.uv.y, 8) * 800 / level_texture_size.y); 143 | 144 | // vanilla: 145 | // distortion.x = floor(distortion.x*_screenSize.x)/_screenSize.x; 146 | // distortion.y = floor(distortion.y*_screenSize.y)/_screenSize.y; 147 | 148 | // modded: 149 | // makes the distortion less pixelated; 150 | distortion.x = floor(distortion.x * level_texture_size.x) / level_texture_size.x; 151 | distortion.y = floor(distortion.y * level_texture_size.y) / level_texture_size.y; 152 | 153 | half4 texcol = SampleTerrainAndLevel(_LevelTex, textCoord+distortion, _spriteRect); 154 | 155 | half plrLightDst = clamp(distance(half2(0,0), half2((i.scrPos.x - i.clr.x)*(_screenSize.x/_screenSize.y), i.scrPos.y - i.clr.y))*lerp(21, 8, i.clr.z), 0, 1); 156 | 157 | 158 | #if RoomHasBrainMold 159 | texcol = lerp(texcol,0.004,_BrainMoldMask(i.scrPos+distortion)); 160 | #endif 161 | half grad = fmod(round(texcol.x * 255)-1, 30.0)/30.0; 162 | half4 grabColor = tex2D(_GrabTexture, half2(i.scrPos.x+distortion.x, i.scrPos.y+distortion.y)); 163 | #if Gutter 164 | 165 | fixed creaturemask = step(max(max(grabColor.x,grabColor.y),grabColor.z),0.0039); 166 | // return creaturemask; 167 | float noise1 = tex2D(_NoiseTex,i.data[0]+distortion).x; 168 | float noise2 = tex2D(_NoiseTex,i.data[1]).x; 169 | float noise3 = tex2D(_NoiseTex,i.data[2]).x; 170 | float noise4 = tex2D(_NoiseTex,i.data[3]).x; 171 | 172 | float noisemix = (noise1-noise2-noise3-noise4+1.)*.25; 173 | float garbagemask = clamp(pulse(.4,pulse(1.,0.4,i.uv.y),noise2*noise4-noise1*noise3)*pulse(1.,0.3,i.uv.y),0,1); 174 | // return noise; 175 | // grad = lerp(grad,noise*.5,grad); 176 | // grad = grad + noisemix; 177 | // return garbagemask; 178 | grad = grad - noisemix*creaturemask*ShEaseOutCubic(grad)+(garbagemask*.3)*creaturemask; 179 | // return grad; 180 | 181 | #endif 182 | 183 | 184 | 185 | plrLightDst = clamp(plrLightDst + pow(1.0-grad, 3), 0, 1); 186 | 187 | if(texcol.x == 1.0 && texcol.y == 1.0 && texcol.z == 1.0) 188 | grad = 1; 189 | 190 | if (grabColor.x == 0.0 && grabColor.y == 2.0/255.0 && grabColor.z == 0.0) 191 | grad = 1; 192 | else if (grad > 6.0/30.0){ 193 | if( grabColor.x > 1.0/255.0 || grabColor.y != 0.0 || grabColor.z != 0.0) 194 | if (grabColor.x == 0.0 && grabColor.y == 1.0/255.0 && grabColor.z == 0.0){ 195 | grad = 1; 196 | grabColor = half4(0,0,0,0); 197 | }else{ 198 | grad = 6.0/30.0; 199 | 200 | grabColor *= lerp(tex2D(_PalTex, float2(5.5/32.0, 7.5/8.0)), half4(1,1,1,1), 0.75); 201 | plrLightDst = 1; 202 | } 203 | }else 204 | grabColor = half4(0,0,0,1); 205 | 206 | 207 | 208 | if(TerrainAndLevelDepth(_LevelTex, textCoord, _spriteRect)<_waterDepth*31.0) return float4(0, 0, 0, 0); 209 | 210 | grad = pow(grad, clamp(1-pow(i.uv.y, 10), 0.5, 1))*i.uv.y; 211 | 212 | half whatToSine = (_RAIN*6) + (tex2D(_NoiseTex, float2((grad/10)+lerp(textCoord.x, 0.5, grad/3)*2.1 + distortion.x, (_RAIN*0.1)+(grad/5)+lerp(textCoord.y, 0.5, grad/3)*2.1+distortion.y) ).x * 7); 213 | half col = (sin(whatToSine * 3.14 * 2)*0.5)+0.5; 214 | 215 | whatToSine = (_RAIN*2.7) + (tex2D(_NoiseTex, float2((grad/7)+lerp(textCoord.x, 0.5, grad/5)*1.3 + distortion.x, (_RAIN*-0.21)+(grad/8)+lerp(textCoord.y, 0.5, grad/6)*1.3+distortion.y) ).x * 6.33); 216 | half col2 = (sin(whatToSine * 3.14 * 2)*0.5)+0.5; 217 | 218 | col = pow(max(col, col2), 47);// * i.uv.y; 219 | 220 | if(col >= 0.8) 221 | grad = min(grad + 0.1*(1.0-abs(0.5-grad)*2.0)* i.uv.y, 1); 222 | 223 | plrLightDst = pow(plrLightDst, 3); 224 | 225 | grad = lerp(grad, pow(grad, lerp(0.2, 1, plrLightDst)), i.clr.z); 226 | 227 | 228 | //clamp((1-pow(i.uv.y, 1.5))*2, 0, 1) 229 | fixed4 pal1 = tex2D(_PalTex, float2(5.5/32.0, 7.5/8.0)); 230 | fixed4 pal2 = tex2D(_PalTex, float2(4.5/32.0, 7.5/8.0)); 231 | #if RIPPLE 232 | fixed rippleMask = allRippleColorMask(i.scrPos); 233 | pal1 = lerp(pal1,tex2D(_GameplayRipplePalTex, float2(5.5/32.0, 7.5/8.0)),rippleMask); 234 | pal2 = lerp(pal2,tex2D(_GameplayRipplePalTex, float2(4.5/32.0, 7.5/8.0)),rippleMask); 235 | #endif 236 | texcol = lerp(pal1, pal2, grad); 237 | if(grabColor.x > 1.0/255.0 || grabColor.y != 0.0 || grabColor.z != 0.0) 238 | { 239 | #if Gutter 240 | return lerp(pal1, pal2, grad+pulse(0.7,.3,1-noisemix)+garbagemask*.2); 241 | #endif 242 | return lerp(texcol, grabColor, pow(clamp((i.uv.y-0.9)*10, 0, 1), 3)); 243 | } else {return texcol;} 244 | 245 | } 246 | ENDCG 247 | 248 | 249 | 250 | } 251 | } 252 | } 253 | } 254 | -------------------------------------------------------------------------------- /SourceCode/MainMod.cs: -------------------------------------------------------------------------------- 1 | 2 | // allows access to private members; 3 | #pragma warning disable CS0618 4 | [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] 5 | #pragma warning restore CS0618 6 | 7 | namespace SBCameraScroll; 8 | 9 | [BepInPlugin("SBCameraScroll", "SBCameraScroll", "3.2.1")] 10 | public class MainMod : BaseUnityPlugin { 11 | // 12 | // meta data 13 | // 14 | 15 | public static readonly string mod_id = "SBCameraScroll"; 16 | public static readonly string author = "SchuhBaum"; 17 | public static readonly string version = "3.2.1"; 18 | public static readonly string mod_directory_path = Directory.GetParent(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)).FullName + Path.DirectorySeparatorChar; 19 | 20 | // 21 | // options 22 | // 23 | 24 | public static bool Option_DynamicZoom => dynamic_zoom.Value; 25 | public static bool Option_FullScreenEffects => full_screen_effects.Value; 26 | public static bool Option_ScrollOneScreenRooms => scroll_one_screen_rooms.Value; 27 | public static bool Option_ReducedMemoryUsage => reduced_memory_usage.Value; 28 | public static bool Option_RippleTrailEffect => ripple_trail_effect.Value; 29 | public static bool Option_CameraOffset => cameraoffset_position.Value; 30 | 31 | // 32 | // other mods 33 | // 34 | 35 | // public static bool is_custom_region_support_enabled = false; 36 | public static bool is_improved_input_enabled = false; 37 | public static bool is_split_screen_coop_enabled = false; 38 | 39 | // 40 | // constants 41 | // 42 | 43 | public static readonly int TextureOffsetArray = Shader.PropertyToID("_textureOffsetArray"); 44 | public static readonly int TextureOffsetArrayLength = Shader.PropertyToID("_textureOffsetArrayLength"); 45 | 46 | // 47 | // variables 48 | // 49 | 50 | public static bool can_log_il_hooks = false; 51 | public static bool is_on_mods_init_initialized = false; 52 | public static bool is_post_mod_init_initialized = false; 53 | 54 | // 55 | // main 56 | // 57 | 58 | public MainMod() { } 59 | 60 | public void OnEnable() { 61 | On.RainWorld.OnModsInit += RainWorld_OnModsInit; 62 | On.RainWorld.PostModsInit += RainWorld_PostModsInit; 63 | } 64 | 65 | // 66 | // public 67 | // 68 | 69 | public static void Initialize_Custom_Input() { 70 | // wrap it in order to make it a soft dependency only; 71 | Debug.Log($"{mod_id}: Initialize custom input."); 72 | RWInputMod.Initialize_Custom_Keybindings(); 73 | PlayerMod.OnEnable(); 74 | } 75 | 76 | // For debugging. 77 | public static void LogAllInstructions(ILContext? context, int index_string_length = 9, int op_code_string_length = 14) { 78 | if (context == null) return; 79 | 80 | Debug.Log("-----------------------------------------------------------------"); 81 | Debug.Log("Log all IL-instructions."); 82 | Debug.Log($"Index:{new string(' ', index_string_length - 6)}OpCode:{new string(' ', op_code_string_length - 7)}Operand:"); 83 | 84 | ILCursor cursor = new(context); 85 | ILCursor label_cursor = cursor.Clone(); 86 | 87 | string cursor_index_string; 88 | string op_code_string; 89 | string operand_string; 90 | 91 | while (true) { 92 | // this might return too early; 93 | // if (cursor.Next.MatchRet()) break; 94 | 95 | // should always break at some point; 96 | // only TryGotoNext() doesn't seem to be enough; 97 | // it still throws an exception; 98 | try { 99 | if (cursor.TryGotoNext(MoveType.Before)) { 100 | cursor_index_string = cursor.Index.ToString(); 101 | cursor_index_string = cursor_index_string.Length < index_string_length ? cursor_index_string + new string(' ', index_string_length - cursor_index_string.Length) : cursor_index_string; 102 | op_code_string = cursor.Next.OpCode.ToString(); 103 | 104 | if (cursor.Next.Operand is ILLabel label) { 105 | label_cursor.GotoLabel(label); 106 | operand_string = $"Label >>> {label_cursor.Index}"; 107 | } else { 108 | operand_string = cursor.Next.Operand?.ToString() ?? ""; 109 | } 110 | 111 | if (operand_string == "") { 112 | Debug.Log(cursor_index_string + op_code_string); 113 | } else { 114 | op_code_string = op_code_string.Length < op_code_string_length ? op_code_string + new string(' ', op_code_string_length - op_code_string.Length) : op_code_string; 115 | Debug.Log(cursor_index_string + op_code_string + operand_string); 116 | } 117 | } else { 118 | break; 119 | } 120 | } catch { 121 | break; 122 | } 123 | } 124 | Debug.Log("-----------------------------------------------------------------"); 125 | } 126 | 127 | // For debugging. 128 | public static void SaveTextureAsPNG(Texture? texture, string path) { 129 | Texture2D? texture_2d = null; 130 | if (texture is RenderTexture render_texture) { 131 | RenderTexture previous = RenderTexture.active; 132 | RenderTexture.active = render_texture; 133 | texture_2d = new Texture2D(render_texture.width, render_texture.height, TextureFormat.RGB24, false); 134 | texture_2d.ReadPixels(new Rect(0, 0, render_texture.width, render_texture.height), 0, 0); 135 | RenderTexture.active = previous; 136 | } else if (texture is Texture2D) { 137 | texture_2d = texture as Texture2D; 138 | } 139 | 140 | if (texture_2d == null) { 141 | return; 142 | } 143 | 144 | byte[] pngData = texture_2d.EncodeToPNG(); 145 | if (pngData != null) { 146 | File.WriteAllBytes(path, pngData); 147 | Debug.Log("Texture saved to " + path); 148 | } else { 149 | Debug.LogWarning("Failed to encode texture to PNG."); 150 | } 151 | } 152 | 153 | // 154 | // private 155 | // 156 | 157 | private void RainWorld_OnModsInit(On.RainWorld.orig_OnModsInit orig, RainWorld rain_world) { 158 | orig(rain_world); 159 | 160 | // if used after isInitialized then disabling and enabling the mod 161 | // without applying removes access to the options menu; 162 | MachineConnector.SetRegisteredOI(mod_id, main_mod_options); 163 | 164 | if (is_on_mods_init_initialized) return; 165 | is_on_mods_init_initialized = true; 166 | 167 | Debug.Log($"{mod_id}: version {version}"); 168 | Debug.Log($"{mod_id}: HasCopyTextureSupport {HasCopyTextureSupport}"); 169 | Debug.Log($"{mod_id}: max_texture_size {SystemInfo.maxTextureSize}"); 170 | Debug.Log($"{mod_id}: mod_directory_path {mod_directory_path}"); 171 | 172 | Load_Asset_Bundle(); 173 | 174 | rain_world.Replace_Shader("Decal"); 175 | rain_world.Replace_Shader("DeepProcessing"); 176 | rain_world.Replace_Shader("DeepWater"); 177 | rain_world.Replace_Shader("DisplaySnowShader"); 178 | rain_world.Replace_Shader("Fog"); 179 | rain_world.Replace_Shader("LevelColor"); 180 | 181 | // There is a bug, where you get heavy flickering when using portals. 182 | // But so far, even replacing the LevelHeat shader with itself doesn't 183 | // fix this. Maybe the provided version is not the same as the actual 184 | // version used in the Watcher DLC. 185 | // 186 | // Vanilla doesn't use the shader `Futile/LevelHeat` but rather the 187 | // shader `Futile/LevelColor` with the keyword `levelheat`. 188 | rain_world.Replace_Shader("LevelHeat", "LevelColor"); 189 | 190 | // Unused currently. 191 | // rain_world.Replace_Shader("PlayerRippleTrail"); 192 | // rain_world.Replace_Shader("ShiftMask"); 193 | // rain_world.Replace_Shader("RippleTearMask"); 194 | 195 | rain_world.Replace_Shader("SporesSnow"); 196 | rain_world.Replace_Shader("UnderWaterLight"); 197 | 198 | Replace_Shader_LevelBlend(); 199 | 200 | foreach (ModManager.Mod mod in ModManager.ActiveMods) { 201 | // if (mod.id == "crs") { 202 | // is_custom_region_support_enabled = true; 203 | // continue; 204 | // } 205 | 206 | if (mod.id == "improved-input-config") { 207 | is_improved_input_enabled = true; 208 | continue; 209 | } 210 | 211 | if (mod.id == "henpemaz_splitscreencoop") { 212 | is_split_screen_coop_enabled = true; 213 | continue; 214 | } 215 | } 216 | 217 | // if (is_custom_region_support_enabled) { 218 | // Debug.Log($"{mod_id}: Custom Region Support (CRS) found. Adept merging when the `REPLACEROOM` feature is used."); 219 | // } else { 220 | // Debug.Log($"{mod_id}: Custom Region Support (CRS) not found."); 221 | // } 222 | 223 | if (is_improved_input_enabled) { 224 | Debug.Log($"{mod_id}: Improved Input Config found. Use custom keybindings."); 225 | } else { 226 | Debug.Log($"{mod_id}: Improved Input Config not found."); 227 | } 228 | 229 | if (is_split_screen_coop_enabled) { 230 | Debug.Log($"{mod_id}: SplitScreen Co-op found. Enable scrolling one-screen rooms."); 231 | } else { 232 | Debug.Log($"{mod_id}: SplitScreen Co-op not found."); 233 | } 234 | 235 | can_log_il_hooks = true; 236 | 237 | AboveCloudsViewMod.OnEnable(); 238 | AbstractRoomMod.OnEnable(); 239 | FScreenMod.OnEnable(); 240 | GhostWorldPresenceMod.OnEnable(); 241 | GoldFlakesMod.OnEnable(); 242 | LevelTexCombinerMod.OnEnable(); 243 | MoreSlugcatsMod.OnEnable(); 244 | OverWorldMod.OnEnable(); 245 | PlayerGraphicsMod.OnEnable(); 246 | RainWorldGameMod.OnEnable(); 247 | RippleCameraDataMod.OnEnable(); 248 | RoomCameraMod.OnEnable(); 249 | RoomMod.OnEnable(); 250 | SuperStructureProjectorMod.OnEnable(); 251 | WaterMod.OnEnable(); 252 | WorldLoaderMod.OnEnable(); 253 | WormGrassPatchMod.OnEnable(); 254 | WormGrassMod.OnEnable(); 255 | 256 | can_log_il_hooks = false; 257 | 258 | } 259 | 260 | private void RainWorld_PostModsInit(On.RainWorld.orig_PostModsInit orig, RainWorld rain_world) { 261 | orig(rain_world); // loads options; 262 | 263 | // this function is called again when applying mods; 264 | // only initialize once; 265 | if (is_post_mod_init_initialized) return; 266 | is_post_mod_init_initialized = true; 267 | 268 | if (is_improved_input_enabled) { 269 | Initialize_Custom_Input(); 270 | } 271 | 272 | main_mod_options.Apply_And_Log_All_Options(); 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /SourceCode/Util.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace SBCameraScroll; 3 | 4 | public static class Util { 5 | public static void Util_ClearTexture(Texture2D tex, Color color) { 6 | Color[] pixels = new Color[tex.width * tex.height]; 7 | for (int i = 0; i < pixels.Length; i++) pixels[i] = color; 8 | tex.SetPixels(pixels); 9 | 10 | // This is required. Futile doesn't handle this for us. 11 | tex.Apply(); 12 | } 13 | 14 | public static string? Util_ExtractRoomNameFromPath(string room_path) { 15 | string[] split = room_path.Split(Path.DirectorySeparatorChar); 16 | if (split.Length == 0) { 17 | return null; 18 | } 19 | 20 | string[] splitted_room_name = split[split.Length-1].Split('_'); 21 | if (splitted_room_name.Length == 0) { 22 | return null; 23 | } 24 | 25 | string room_name; 26 | if (splitted_room_name[0].ToLower() == "gate") { 27 | if (splitted_room_name.Length <= 2) return null; 28 | room_name = $"gate_{splitted_room_name[1]}_{splitted_room_name[2]}"; 29 | } else { 30 | room_name = $"{splitted_room_name[0]}_{splitted_room_name[1]}"; 31 | } 32 | 33 | return room_name.ToLower(); 34 | } 35 | 36 | // 37 | // 38 | 39 | public static (Vector2[] camera_positions, RectInt rectangle)? Util_GetCameraPositionsAndLevelTextureRectangle(string room_name) { 40 | if (Custom.rainWorld?.processManager?.currentMainLoop is not RainWorldGame game) { 41 | Debug.Log($"{mod_id}.Util_LoadRoomTextureIntoRenderTexture: [WARNING] Expected to be in-game. But I did not find the instance for RainWorldGame. Aborting."); 42 | return null; 43 | } 44 | 45 | if (CalculateLevelTextureRectangle(room_name) is not RectInt rect) { 46 | Debug.Log($"{mod_id}.Util_LoadRoomTextureIntoRenderTexture: [WARNING] Could not calculate the level texture rectangle. Aborting."); 47 | return null; 48 | } 49 | 50 | Vector2[]? camera_positions = LoadCameraPositions(room_name); 51 | if (camera_positions == null) { 52 | Debug.Log($"{mod_id}.Util_LoadRoomTextureIntoRenderTexture: [WARNING] Could not load camera positions. Aborting."); 53 | return null; 54 | } 55 | 56 | CheckCameraPositions(ref camera_positions); 57 | if (camera_positions == null || camera_positions.Length == 0) { 58 | Debug.Log($"{mod_id}.Util_LoadRoomTextureIntoRenderTexture: [WARNING] Could not load camera positions. Aborting."); 59 | return null; 60 | } 61 | return (camera_positions, rect); 62 | } 63 | 64 | public static bool Util_UpdateRenderTexture(RenderTexture render_texture, RectInt rectangle) { 65 | int total_width = rectangle.width; 66 | int total_height = rectangle.height; 67 | if (total_width > maximum_texture_width || total_height > maximum_texture_height) { 68 | Debug.Log($"{mod_id}.Util_LoadRoomTextureIntoRenderTexture: Warning! Merged texture width or height is too large. Setting to the maximum and hoping for the best."); 69 | total_width = Mathf.Min(total_width, maximum_texture_width); 70 | total_height = Mathf.Min(total_height, maximum_texture_height); 71 | } 72 | 73 | if (total_width > SystemInfo.maxTextureSize || total_height > SystemInfo.maxTextureSize) { 74 | return false; 75 | } 76 | 77 | if (render_texture.width != total_width || render_texture.height != total_height) { 78 | render_texture.Release(); 79 | render_texture.width = total_width; 80 | render_texture.height = total_height; 81 | 82 | RenderTexture active_render_texture = RenderTexture.active; 83 | RenderTexture.active = render_texture; 84 | GL.Clear(clearDepth: false, clearColor: true, new Color(1f/255f, 0f, 0f)); 85 | RenderTexture.active = active_render_texture; 86 | } 87 | return true; 88 | } 89 | 90 | // 91 | 92 | [Obsolete("Use Util_LoadRoomTextureIntoRenderTexture(string room_name, RenderTexture render_texture, [Texture2D cache]) or Util_LoadRoomTextureIntoRenderTexture(RoomCamera room_camera, string room_name, [RenderTexture render_texture]) instead.")] 93 | public static void Util_LoadRoomTextureIntoRenderTexture(string room_name, RenderTexture render_texture, int? use_cache_camera_number = null) { 94 | if (use_cache_camera_number == null) { 95 | Util_LoadRoomTextureIntoRenderTexture(room_name, render_texture, (Texture2D?)null); 96 | } else { 97 | Util_LoadRoomTextureIntoRenderTexture(room_name, render_texture, (int)use_cache_camera_number); 98 | } 99 | } 100 | 101 | // 102 | 103 | public static Texture2D camera_texture = new Texture2D(1400, 800, TextureFormat.ARGB32, mipChain: false) { 104 | anisoLevel = 0, 105 | filterMode = FilterMode.Point, 106 | wrapMode = TextureWrapMode.Clamp 107 | }; 108 | 109 | public static bool Util_LoadRoomTextureIntoRenderTexture(string room_name, RenderTexture render_texture, Texture2D? cache = null) { 110 | if (Util_GetCameraPositionsAndLevelTextureRectangle(room_name) is not (Vector2[] camera_positions, RectInt rect)) { 111 | return false; 112 | } 113 | 114 | if (!Util_UpdateRenderTexture(render_texture, rect)) { 115 | return false; 116 | } 117 | 118 | if (cache == null) { 119 | cache = Util.camera_texture; 120 | } 121 | 122 | Vector2 min_camera_position = new Vector2(rect.x, rect.y); 123 | for (int cam_pos_index = 0; cam_pos_index < camera_positions.Length; ++cam_pos_index) { 124 | Vector2 texture_offset = camera_positions[cam_pos_index] - min_camera_position; 125 | 126 | int x = (int)texture_offset.x; 127 | int y = (int)texture_offset.y; 128 | int cutoff_x = 0; 129 | int cutoff_y = 0; 130 | 131 | if (x < 0) cutoff_x = -x; 132 | if (y < 0) cutoff_y = -y; 133 | 134 | if (x < maximum_texture_width && y < maximum_texture_height) { 135 | int width = Math.Min(1400 - cutoff_x, maximum_texture_width - x); 136 | int height = Math.Min(800 - cutoff_y, maximum_texture_height - y); 137 | 138 | // Kinda wasteful. The Texture2D is just a buffer but it lives 139 | // on the CPU as well as the GPU. I only need the GPU. But there 140 | // doesn't seem to be a direct way to upload a png file to the 141 | // GPU. 142 | string camera_texture_path = WorldLoader.FindRoomFile(room_name, includeRootDirectory: true, $"_{cam_pos_index+1}.png"); 143 | byte[] bytes = AssetManager.PreLoadTexture(camera_texture_path); 144 | 145 | // Marking it prevents the copy in the RAM. But you cannot call 146 | // GetPixel(), etc. 147 | cache.LoadImage(bytes, markNonReadable: true); 148 | 149 | Graphics.CopyTexture(cache, 0, 0, cutoff_x, cutoff_y, width, height, render_texture, 0, 0, Mathf.Max(x, 0), Mathf.Max(y, 0)); 150 | } 151 | } 152 | return true; 153 | } 154 | 155 | public static bool Util_LoadRoomTextureIntoRenderTexture(RoomCamera room_camera, string room_name, RenderTexture? render_texture = null) { 156 | if (Util_GetCameraPositionsAndLevelTextureRectangle(room_name) is not (Vector2[] camera_positions, RectInt rect)) { 157 | return false; 158 | } 159 | 160 | if (render_texture == null) { 161 | render_texture = room_camera.Render_Texture(); 162 | } 163 | if (!Util_UpdateRenderTexture(render_texture, rect)) { 164 | return false; 165 | } 166 | 167 | int camera_number = room_camera.cameraNumber; 168 | if (camera_number < 0 || camera_number > 3) { 169 | Debug.Log($"{mod_id}.Util_CacheAndLoadRoomTextureIntoRenderTexture: [WARNING] Invalid camera number {camera_number}. Use 0 instead."); 170 | camera_number = 0; 171 | } 172 | 173 | Vector2 min_camera_position = new Vector2(rect.x, rect.y); 174 | for (int cam_pos_index = 0; cam_pos_index < camera_positions.Length; ++cam_pos_index) { 175 | Vector2 texture_offset = camera_positions[cam_pos_index] - min_camera_position; 176 | 177 | int x = (int)texture_offset.x; 178 | int y = (int)texture_offset.y; 179 | int cutoff_x = 0; 180 | int cutoff_y = 0; 181 | 182 | if (x < 0) cutoff_x = -x; 183 | if (y < 0) cutoff_y = -y; 184 | 185 | if (x < maximum_texture_width && y < maximum_texture_height) { 186 | int width = Math.Min(1400 - cutoff_x, maximum_texture_width - x); 187 | int height = Math.Min(800 - cutoff_y, maximum_texture_height - y); 188 | 189 | if (room_name != Get_Level_Texture_Room_Name(camera_number, cam_pos_index)) { 190 | // Load and cache. 191 | string camera_texture_path = WorldLoader.FindRoomFile(room_name, includeRootDirectory: true, $"_{cam_pos_index+1}.png"); 192 | byte[] bytes = AssetManager.PreLoadTexture(camera_texture_path); 193 | RoomCameraMod_LoadOneScreenImage(room_camera, room_name, cam_pos_index, bytes); 194 | } 195 | 196 | Graphics.CopyTexture(Get_Level_Texture(camera_number, cam_pos_index), 0, 0, cutoff_x, cutoff_y, width, height, render_texture, 0, 0, Mathf.Max(x, 0), Mathf.Max(y, 0)); 197 | } 198 | } 199 | return true; 200 | } 201 | 202 | // 203 | // 204 | 205 | private static readonly int _capacity = 16 * 1024; 206 | public static Dictionary[] camera_number_to_cached_pixel_colors = { 207 | new(_capacity), new(_capacity), new(_capacity), new(_capacity) 208 | }; 209 | public static Dictionary Get_Cached_Pixel_Colors(this RoomCamera room_camera) { 210 | var camera_number = room_camera.cameraNumber; 211 | if (camera_number < 0 || camera_number > 3) { 212 | Debug.Log($"{mod_id}_Get_Cached_Pixel_Colors: [WARNING] Unknown camera number {camera_number}."); 213 | camera_number = 0; 214 | } 215 | return camera_number_to_cached_pixel_colors[camera_number]; 216 | } 217 | 218 | // 219 | 220 | public static void Util_ClearCachedPixelColors() 221 | { 222 | foreach(var cached_pixel_colors in camera_number_to_cached_pixel_colors) { 223 | cached_pixel_colors.Clear(); 224 | } 225 | } 226 | 227 | private static Texture2D pixel_texture = new Texture2D(1, 1, TextureFormat.RGB24, false); 228 | public static Color Util_ReadPixelColorFromCacheOrGPU(RoomCamera room_camera, Vector2 position) 229 | { 230 | Color pixel_color; 231 | 232 | var cached_pixel_colors = room_camera.Get_Cached_Pixel_Colors(); 233 | if (cached_pixel_colors.TryGetValue(position, out pixel_color)) 234 | { 235 | return pixel_color; 236 | } 237 | 238 | // NOTE: We need to flip the y-coordinates for Unity's rectangles. 239 | var render_texture = room_camera.Render_Texture(); 240 | var height = render_texture.height; 241 | 242 | RenderTexture previous = RenderTexture.active; 243 | RenderTexture.active = render_texture; 244 | pixel_texture.ReadPixels(new Rect(Mathf.FloorToInt(position.x), height-Mathf.FloorToInt(position.y), 1, 1), 0, 0); 245 | RenderTexture.active = previous; 246 | 247 | pixel_color = pixel_texture.GetPixel(0,0); 248 | cached_pixel_colors.Add(position, pixel_color); 249 | return pixel_color; 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /SourceCode/TypeCameras/VanillaTypeCamera.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace SBCameraScroll; 3 | 4 | public class VanillaTypeCamera : IAmATypeCamera { 5 | // 6 | // parameters 7 | // 8 | 9 | // distance from border instead of camera position; 10 | public static float camera_box_from_border_x = 180f; 11 | public static float camera_box_from_border_y = 20f; 12 | 13 | private readonly RoomCamera _room_camera; 14 | private readonly RoomCameraFields _room_camera_fields; 15 | 16 | // 17 | // variables 18 | // 19 | 20 | public EntityID? follow_abstract_creature_id = null; 21 | 22 | public Vector2 seek_position = new(); 23 | public Vector2 vanilla_type_position = new(); 24 | 25 | // splitting when using split screen can happen dynamically; when split then you 26 | // might not have enough space to use vanilla positions; 27 | public bool Are_Vanilla_Positions_Forced_Disabled => is_split_screen_coop_enabled && (Is_Split_Horizontally || Is_Split_Vertically || Is_Split_4Screen && !is_camera_zoomed_out_in_four_player_split_screen); 28 | public bool are_vanilla_positions_used; 29 | public bool is_camera_zoomed_out_in_four_player_split_screen = false; 30 | public bool is_centered = false; 31 | 32 | public int transition_counter = 0; 33 | 34 | // 35 | // main 36 | // 37 | 38 | public VanillaTypeCamera(RoomCamera room_camera, RoomCameraFields room_camera_fields) { 39 | _room_camera = room_camera; 40 | _room_camera_fields = room_camera_fields; 41 | are_vanilla_positions_used = camera_zoom <= 1.33f; 42 | } 43 | 44 | // 45 | // public 46 | // 47 | 48 | public bool Is_Map_Pressed(Player player) { 49 | if (!is_improved_input_enabled) return player.input[0].mp && !player.input[1].mp; 50 | return player.Wants_To_Center_Camera(); 51 | } 52 | 53 | public void Move_Camera() { 54 | Vector2 half_screen_size = 0.5f * _room_camera.sSize; 55 | float camera_box_multiplier_x = 1f; 56 | float camera_box_multiplier_y = 1f; 57 | 58 | if (is_split_screen_coop_enabled) { 59 | if (Is_Split_Horizontally) { 60 | half_screen_size.y -= 0.25f * _room_camera.sSize.y; 61 | camera_box_multiplier_y = 0.5f; 62 | } else if (Is_Split_Vertically) { 63 | half_screen_size.x -= 0.25f * _room_camera.sSize.x; 64 | camera_box_multiplier_x = 0.5f; 65 | } else if (Is_Split_4Screen && !is_camera_zoomed_out_in_four_player_split_screen) { 66 | half_screen_size.x -= 0.25f * _room_camera.sSize.x; 67 | half_screen_size.y -= 0.25f * _room_camera.sSize.y; 68 | camera_box_multiplier_x = 0.5f; 69 | camera_box_multiplier_y = 0.5f; 70 | } 71 | } else if (Is_Camera_Zoom_Enabled) { 72 | half_screen_size += Half_Inverse_Camera_Zoom_XY * _room_camera.sSize; 73 | camera_box_multiplier_x = 1f / camera_zoom; 74 | camera_box_multiplier_y = 1f / camera_zoom; 75 | } 76 | 77 | float direction_x = Math.Sign(_room_camera_fields.on_screen_position.x - vanilla_type_position.x); 78 | float distance_x = direction_x * (_room_camera_fields.on_screen_position.x - vanilla_type_position.x); 79 | float start_lean_distance_x = 2f * Mathf.Abs(_room_camera.followCreatureInputForward.x); 80 | 81 | if (distance_x > half_screen_size.x - camera_box_multiplier_x * camera_box_from_border_x) { 82 | // I cannot use ResetCameraPosition() because it sets vanilla_type_position to on_screen_position and useVanillaPositions is set to true; 83 | seek_position.x = 0.0f; 84 | 85 | // new distance to the center of the screen: outerCameraBoxX + 50f; 86 | // leanStartDistanceX can be up to 40f; 87 | vanilla_type_position.x += direction_x * (distance_x + half_screen_size.x - camera_box_multiplier_x * camera_box_from_border_x - 50f); 88 | _room_camera.lastPos.x = vanilla_type_position.x; // prevent transition with in-between frames 89 | _room_camera.pos.x = vanilla_type_position.x; 90 | } else { 91 | // lean effect; 92 | // 20f for start_lean_distance_x is a simplification; 93 | // vanilla scales this instead; 94 | if (distance_x > half_screen_size.x - camera_box_multiplier_x * camera_box_from_border_x - start_lean_distance_x) { 95 | seek_position.x = direction_x * 8f; 96 | } else { 97 | seek_position.x *= 0.9f; 98 | } 99 | 100 | // mimic what vanilla is doing with roomCamera.leanPos in Update(); 101 | _room_camera.pos.x = Mathf.Lerp(_room_camera.lastPos.x, vanilla_type_position.x + seek_position.x, 0.1f); 102 | } 103 | 104 | float direction_y = Math.Sign(_room_camera_fields.on_screen_position.y - vanilla_type_position.y); 105 | float distance_y = direction_y * (_room_camera_fields.on_screen_position.y - vanilla_type_position.y); 106 | float start_lean_distance_y = 2f * Mathf.Abs(_room_camera.followCreatureInputForward.y); 107 | 108 | if (distance_y > half_screen_size.y - camera_box_multiplier_y * camera_box_from_border_y) { 109 | seek_position.y = 0.0f; 110 | vanilla_type_position.y += direction_y * (distance_y + half_screen_size.y - camera_box_multiplier_y * camera_box_from_border_y - 50f); 111 | _room_camera.lastPos.y = vanilla_type_position.y; 112 | _room_camera.pos.y = vanilla_type_position.y; 113 | } else { 114 | // vanilla does not do the lean effect for both; 115 | if (distance_y > half_screen_size.y - camera_box_multiplier_y * camera_box_from_border_y - start_lean_distance_y && seek_position.x < 8f) { 116 | seek_position.y = direction_y * 8f; 117 | } else { 118 | seek_position.y *= 0.9f; 119 | } 120 | _room_camera.pos.y = Mathf.Lerp(_room_camera.lastPos.y, vanilla_type_position.y + seek_position.y, 0.1f); 121 | } 122 | 123 | CheckBorders(_room_camera, ref vanilla_type_position); 124 | CheckBorders(_room_camera, ref _room_camera.lastPos); 125 | CheckBorders(_room_camera, ref _room_camera.pos); 126 | } 127 | 128 | public bool Move_Camera_Transition() { 129 | Vector2 target_position; 130 | if (!are_vanilla_positions_used || Are_Vanilla_Positions_Forced_Disabled) { 131 | target_position = _room_camera_fields.on_screen_position; 132 | CheckBorders(_room_camera, ref target_position); // stop at borders 133 | } else { 134 | // only in case when the player is not the target 135 | // seekPos can change during a transition 136 | // this extends the transition until the player stops changing screens 137 | target_position = _room_camera.seekPos; 138 | } 139 | 140 | // 3f is not enough to reach the player that is walking away from the camera; 141 | // use a counter as well; 142 | _room_camera.pos = Vector2.Lerp(_room_camera.lastPos, target_position, smoothing_factor); 143 | _room_camera.pos = Custom.MoveTowards(_room_camera.pos, target_position, 3f); 144 | return _room_camera.pos == target_position; 145 | } 146 | 147 | public void Reset() { 148 | UpdateOnScreenPosition(_room_camera); 149 | CheckBorders(_room_camera, ref _room_camera_fields.on_screen_position); // do not move past room boundaries 150 | 151 | _room_camera.seekPos = _room_camera.CamPos(_room_camera.currentCameraPosition); 152 | _room_camera.seekPos.x += _room_camera.hDisplace + 8f; 153 | _room_camera.seekPos.y += 18f; 154 | _room_camera.leanPos *= 0.0f; 155 | 156 | are_vanilla_positions_used = camera_zoom <= 1.33f; 157 | if (!are_vanilla_positions_used || Are_Vanilla_Positions_Forced_Disabled) { 158 | _room_camera.lastPos = _room_camera_fields.on_screen_position; 159 | _room_camera.pos = _room_camera_fields.on_screen_position; 160 | } else { 161 | // center camera on vanilla position; 162 | _room_camera.lastPos = _room_camera.seekPos; 163 | _room_camera.pos = _room_camera.seekPos; 164 | } 165 | 166 | follow_abstract_creature_id = null; // do a smooth transition // this actually makes a difference for the vanilla type camera // otherwise the map input would immediately be processed 167 | seek_position *= 0.0f; 168 | vanilla_type_position = _room_camera_fields.on_screen_position; 169 | is_centered = false; 170 | } 171 | 172 | public void Update() { 173 | if (_room_camera.followAbstractCreature == null) return; 174 | if (_room_camera.room == null) return; 175 | UpdateOnScreenPosition(_room_camera); 176 | 177 | // do a smooth transition when zoom has changed in split screen; this can happen 178 | // dynamically; 179 | if (is_split_screen_coop_enabled && Is_Split_4Screen && is_camera_zoomed_out_in_four_player_split_screen != Is_4Screen_Zoomed_Out(_room_camera)) { 180 | is_camera_zoomed_out_in_four_player_split_screen = Is_4Screen_Zoomed_Out(_room_camera); 181 | follow_abstract_creature_id = null; 182 | } 183 | 184 | // smooth transition when switching cameras in the same room 185 | if (follow_abstract_creature_id != _room_camera.followAbstractCreature.ID && _room_camera.followAbstractCreature?.realizedCreature is Creature creature) { 186 | // keep transition going even when switching back; 187 | follow_abstract_creature_id = null; 188 | 189 | // needs follow_abstract_creature_id = null; 190 | // updates camera_offset; 191 | if (Move_Camera_Transition() || transition_counter > 20) { 192 | follow_abstract_creature_id = _room_camera.followAbstractCreature.ID; 193 | vanilla_type_position = _room_camera.pos; 194 | is_centered = true; 195 | transition_counter = 0; 196 | return; 197 | } 198 | 199 | ++transition_counter; 200 | if (camera_zoom > 1.33f) { 201 | // vanilla positions don't respect split screen; 202 | // otherwise you can move off-screen between camera positions; 203 | are_vanilla_positions_used = false; 204 | } else if (creature is Player player && Is_Map_Pressed(player)) { 205 | are_vanilla_positions_used = !are_vanilla_positions_used; 206 | } 207 | return; 208 | } 209 | 210 | if (is_centered && (Mathf.Abs(_room_camera_fields.on_screen_position.x - _room_camera_fields.last_on_screen_position.x) > 1f || Mathf.Abs(_room_camera_fields.on_screen_position.y - _room_camera_fields.last_on_screen_position.y) > 1f)) { 211 | is_centered = false; 212 | } 213 | 214 | if (!are_vanilla_positions_used || Are_Vanilla_Positions_Forced_Disabled) { 215 | Move_Camera(); 216 | } 217 | 218 | // in Safari mode the camera might follow other creatures; 219 | // this means that inputs are ignored; 220 | // this means that you can't center the camera and it is 221 | // just the vanilla camera; 222 | { 223 | if (_room_camera.followAbstractCreature?.realizedCreature is not Player player) return; 224 | if (!Is_Map_Pressed(player)) return; 225 | 226 | if (camera_zoom > 1.33f) { 227 | are_vanilla_positions_used = false; 228 | } else if (are_vanilla_positions_used || is_centered) { 229 | are_vanilla_positions_used = !are_vanilla_positions_used; 230 | } 231 | follow_abstract_creature_id = null; // start a smooth transition 232 | } 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /ModdedShaders/LevelHeat.shader: -------------------------------------------------------------------------------- 1 | 2 | //from http://forum.unity3d.com/threads/68402-Making-a-2D-game-for-iPhone-iPad-and-need-better-performance 3 | 4 | Shader "SBCameraScroll/LevelHeat" //Unlit Transparent Vertex Colored Additive 5 | { 6 | Properties 7 | { 8 | _MainTex("Base (RGB) Trans (A)", 2D) = "white" {} 9 | 10 | // _PalTex ("Base (RGB) Trans (A)", 2D) = "white" {} 11 | 12 | // _NoiseTex ("Base (RGB) Trans (A)", 2D) = "white" {} 13 | 14 | // _RAIN ("Rain", Range (0,1.0)) = 0.5 15 | //_Color ("Main Color", Color) = (1,0,0,1.5) 16 | //_BlurAmount ("Blur Amount", Range(0,02)) = 0.0005 17 | } 18 | 19 | Category 20 | { 21 | Tags{ "Queue" = "Transparent" "IgnoreProjector" = "True" "RenderType" = "Transparent" } 22 | ZWrite Off 23 | //Alphatest Greater 0 24 | Blend SrcAlpha OneMinusSrcAlpha 25 | Fog{ Color(0,0,0,0) } 26 | Lighting Off 27 | Cull Off //we can turn backface culling off because we know nothing will be facing backwards 28 | 29 | BindChannels 30 | { 31 | Bind "Vertex", vertex 32 | Bind "texcoord", texcoord 33 | Bind "Color", color 34 | } 35 | 36 | SubShader 37 | { 38 | GrabPass { } 39 | 40 | Pass 41 | { 42 | //SetTexture [_MainTex] 43 | //{ 44 | // Combine texture * primary 45 | //} 46 | 47 | CGPROGRAM 48 | #pragma target 3.0 49 | #pragma vertex vert 50 | #pragma fragment frag 51 | #include "UnityCG.cginc" 52 | //#pragma profileoption NumTemps=64 53 | //#pragma profileoption NumInstructionSlots=2048 54 | 55 | //float4 _Color; 56 | sampler2D _MainTex; 57 | 58 | // vanilla: 59 | // uniform float2 _MainTex_TexelSize; 60 | 61 | // modded: 62 | // in theory they are the same since this shader is only applied to the level 63 | // texture; but they are not for some reason; 64 | uniform float2 _LevelTex_TexelSize; 65 | 66 | sampler2D _PalTex; 67 | sampler2D _NoiseTex; 68 | #if defined(SHADER_API_PSSL) 69 | sampler2D _GrabTexture; 70 | #else 71 | sampler2D _GrabTexture : register(s0); 72 | #endif 73 | 74 | //float _BlurAmount; 75 | 76 | uniform float _palette; 77 | uniform float _RAIN; 78 | uniform float _light = 0; 79 | uniform float4 _spriteRect; 80 | uniform float2 _screenOffset; 81 | 82 | uniform float4 _lightDirAndPixelSize; 83 | uniform float _fogAmount; 84 | uniform float _waterLevel; 85 | uniform float _Grime; 86 | uniform float _SwarmRoom; 87 | uniform float _WetTerrain; 88 | uniform float _cloudsSpeed; 89 | uniform float _darkness; 90 | uniform float _contrast; 91 | uniform float _saturation; 92 | uniform float _hue; 93 | uniform float _brightness; 94 | uniform half4 _AboveCloudsAtmosphereColor; 95 | uniform fixed _rimFix; 96 | 97 | struct v2f { 98 | float4 pos : SV_POSITION; 99 | float2 uv : TEXCOORD0; 100 | float2 uv2 : TEXCOORD1; 101 | float4 clr : COLOR; 102 | }; 103 | 104 | float4 _MainTex_ST; 105 | 106 | v2f vert(appdata_full v) 107 | { 108 | v2f o; 109 | o.pos = UnityObjectToClipPos (v.vertex); 110 | o.uv = TRANSFORM_TEX(v.texcoord, _MainTex); 111 | 112 | // vanilla: 113 | // o.uv2 = o.uv-_MainTex_TexelSize*.5*_rimFix; 114 | 115 | // modded: 116 | o.uv2 = o.uv - _LevelTex_TexelSize * .5 * _rimFix; 117 | 118 | o.clr = v.color; 119 | return o; 120 | } 121 | 122 | inline float3 applyHue(float3 aColor, float aHue) 123 | { 124 | float angle = radians(aHue); 125 | float3 k = float3(0.57735, 0.57735, 0.57735); 126 | float cosAngle = cos(angle); 127 | //Rodrigues' rotation formula 128 | return aColor * cosAngle + cross(k, aColor) * sin(angle) + k * dot(k, aColor) * (1 - cosAngle); 129 | } 130 | 131 | half4 frag(v2f i) : SV_Target 132 | { 133 | half4 setColor = half4(0.0, 0.0, 0.0, 1.0); 134 | bool checkMaskOut = false; 135 | 136 | float ugh = fmod(fmod(round(tex2D(_MainTex, float2(i.uv.x, i.uv.y)).x * 255), 90) - 1, 30) / 300.0; 137 | float displace = tex2D(_NoiseTex, float2((i.uv.x * 1.5) - ugh + (_RAIN*0.01), (i.uv.y*0.25) - ugh + _RAIN * 0.05)).x; 138 | displace = clamp((sin((displace + i.uv.x + i.uv.y + _RAIN*0.1) * 3 * 3.14) - 0.95) * 20, 0, 1); 139 | 140 | half2 screenPos = half2(lerp(_spriteRect.x+_screenOffset.x, _spriteRect.z+_screenOffset.x, i.uv.x), lerp(_spriteRect.y+_screenOffset.y, _spriteRect.w+_screenOffset.y, i.uv.y)); 141 | #if UNITY_UV_STARTS_AT_TOP 142 | screenPos.y = 1 - screenPos.y; 143 | #endif 144 | 145 | if (_WetTerrain < 0.5 || 1 - screenPos.y > _waterLevel) 146 | displace = 0; 147 | 148 | half4 texcol = tex2D(_MainTex, float2(i.uv2.x, i.uv2.y+displace*0.001)); 149 | 150 | int red = round(texcol.x * 255); 151 | int green = round(texcol.y * 255); 152 | 153 | float effectCol = 0; 154 | 155 | if (texcol.x == 1.0 && texcol.y == 1.0 && texcol.z == 1.0) { 156 | setColor = tex2D(_PalTex, float2(0.5 / 32.0, 7.5 / 8)); 157 | if(_rimFix>.5){ 158 | setColor = _AboveCloudsAtmosphereColor; 159 | } 160 | checkMaskOut = true; 161 | } 162 | else 163 | { 164 | half notFloorDark = 1; 165 | if (green >= 16) { 166 | notFloorDark = 0; 167 | green -= 16; 168 | } 169 | if (green >= 8) { 170 | green -= 8; 171 | } 172 | 173 | 174 | // vanilla: 175 | // half shadow = tex2D(_NoiseTex, float2((i.uv.x*0.5) + (_RAIN*0.1*_cloudsSpeed) - (0.003*fmod(red, 30.0)), 1 - (i.uv.y*0.5) + (_RAIN*0.2*_cloudsSpeed) - (0.003*fmod(red, 30.0)))).x; 176 | // shadow = 0.5 + sin(fmod(shadow + (_RAIN*0.1*_cloudsSpeed) - i.uv.y, 1)*3.14 * 2)*0.5; 177 | 178 | // modded: 179 | // The clouds light mask can be stretched. The mask is 180 | // applied to the full level texture. Merged level 181 | // textures contain multiple screens. 182 | float number_of_screens_x = _spriteRect.z - _spriteRect.x; 183 | float number_of_screens_y = _spriteRect.w - _spriteRect.y; 184 | half shadow = tex2D(_NoiseTex, float2(((number_of_screens_x*i.uv.x*0.5) + (_RAIN*0.1*_cloudsSpeed) - (0.003*fmod(red, 30.0))), (1-(number_of_screens_y*i.uv.y*0.5) + (_RAIN*0.2*_cloudsSpeed) - (0.003*fmod(red, 30.0))))).x; 185 | shadow = 0.5 + sin(fmod(shadow+(_RAIN*0.1*_cloudsSpeed)-number_of_screens_y*i.uv.y, 1)*3.14*2)*0.5; 186 | 187 | // vanilla: 188 | shadow = clamp(((shadow - 0.5) * 6) + 0.5 - (_light * 4), 0, 1); 189 | 190 | if (red > 90) 191 | red -= 90; 192 | else 193 | shadow = 1.0; 194 | 195 | int paletteColor = clamp(floor((red - 1) / 30.0), 0, 2); //some distant objects want to get palette color 3, so we clamp it 196 | 197 | red = fmod(red - 1, 30.0); 198 | 199 | if (shadow != 1 && red >= 5) { 200 | half2 grabPos = float2(screenPos.x + -_lightDirAndPixelSize.x*_lightDirAndPixelSize.z*(red - 5), 1 - screenPos.y + _lightDirAndPixelSize.y*_lightDirAndPixelSize.w*(red - 5)); 201 | grabPos = ((grabPos - half2(0.5, 0.3))*(1 + (red - 5.0) / 460.0)) + half2(0.5, 0.3); 202 | float4 grabTexCol = tex2D(_GrabTexture, grabPos); 203 | if (grabTexCol.x != 0.0 || grabTexCol.y != 0.0 || grabTexCol.z != 0.0){ 204 | //red = 5; 205 | shadow = 1; 206 | } 207 | } 208 | 209 | // ============ 210 | if (red >= 5) { 211 | float4 grabTexCol = tex2D(_GrabTexture, float2(screenPos.x, 1 - screenPos.y)); 212 | if (grabTexCol.x > 1.0 / 255.0 || grabTexCol.y != 0.0 || grabTexCol.z != 0.0) { 213 | red = 5; 214 | } 215 | } 216 | 217 | effectCol = tex2D(_NoiseTex, half2(i.uv.x, i.uv.y*0.5 - _RAIN*0.123)); 218 | effectCol = 0.5 + 0.5 * sin(effectCol*3.14 * 8 + i.uv.y*36.231 - _RAIN*2.63 + (red / 30.0)*8.12); 219 | effectCol *= 0.5 + 0.5 * sin(tex2D(_NoiseTex, half2((1.0 - i.uv.x) * 2, i.uv.y*0.5 - _RAIN*0.1862))*3.14 * 8 + i.uv.y*50.231 - _RAIN*4.75442 + (red / 30.0)*8.12); 220 | effectCol = 1 - effectCol; 221 | 222 | effectCol = pow(effectCol, 30); 223 | 224 | texcol = tex2D(_MainTex, float2(i.uv.x, i.uv.y + lerp(-0.01, 0.02, effectCol)*i.clr.w)); 225 | //screenPos = half2(lerp(_spriteRect.x+_screenOffset.x, _spriteRect.z+_screenOffset.x, i.uv.x), lerp(_spriteRect.y+_screenOffset.y, _spriteRect.w+_screenOffset.y, i.uv.y));// + lerp(-0.01, 0.02, effectCol)*i.clr.w)); 226 | //#if UNITY_UV_STARTS_AT_TOP 227 | // screenPos.y = 1 - screenPos.y; 228 | //#endif 229 | //screenPos.x = (floor(screenPos.x * 1366) + 0.5) / 1366; 230 | //screenPos.y = (floor(screenPos.y * 768) + 0.5) / 768; 231 | red = round(texcol.x * 255); 232 | red = fmod(red - 1, 30.0); 233 | 234 | effectCol = 1 - abs(effectCol - 0.5) * 2; 235 | effectCol = pow(max(0,effectCol - (1.0 - i.clr.w)), lerp(10.0, 1.0, i.clr.w)); 236 | 237 | 238 | 239 | ////setColor = tex2D(_PalTex, float2((red*notFloorDark) / 32.0, (paletteColor + 0.5) / 8.0)); 240 | setColor = lerp(tex2D(_PalTex, float2((red*notFloorDark) / 32.0, (paletteColor + 3 + 0.5) / 8.0)), tex2D(_PalTex, float2((red*notFloorDark) / 32.0, (paletteColor + 0.5) / 8.0)), shadow); 241 | 242 | if (green >= 8) 243 | effectCol = 100; 244 | else if (green > 0 && green < 3) 245 | effectCol = green; 246 | // ============== 247 | 248 | half rbcol = (sin((_RAIN + (tex2D(_NoiseTex, float2(i.uv.x * 2, i.uv.y * 2)).x * 4) + red / 12.0) * 3.14 * 2)*0.5) + 0.5; 249 | setColor = lerp(setColor, tex2D(_PalTex, float2((5.5 + rbcol * 25) / 32.0, 6.5 / 8.0)), (green >= 4 ? 0.2 : 0.0) * _Grime); 250 | if (effectCol == 100) { 251 | half4 decalCol = tex2D(_MainTex, float2((255.5 - round(texcol.z*255.0)) / 1400.0, 799.5 / 800.0)); 252 | if (paletteColor == 2) decalCol = lerp(decalCol, half4(1, 1, 1, 1), 0.2 - shadow*0.1); 253 | decalCol = lerp(decalCol, tex2D(_PalTex, float2(1.5 / 32.0, 7.5 / 8.0)), red / 60.0); 254 | setColor = lerp(lerp(setColor, decalCol, 0.7), setColor*decalCol*1.5, lerp(0.9, 0.3 + 0.4*shadow, clamp((red - 3.5)*0.3, 0, 1))); 255 | } 256 | else if (green > 0 && green < 3) { 257 | setColor = lerp(setColor, lerp(lerp(tex2D(_PalTex, float2(30.5 / 32.0, (5.5 - (effectCol - 1) * 2) / 8.0)), tex2D(_PalTex, float2(31.5 / 32.0, (5.5 - (effectCol - 1) * 2) / 8.0)), shadow), lerp(tex2D(_PalTex, float2(30.5 / 32.0, (4.5 - (effectCol - 1) * 2) / 8.0)), tex2D(_PalTex, float2(31.5 / 32.0, (4.5 - (effectCol - 1) * 2) / 8.0)), shadow), red / 30.0), texcol.z); 258 | } 259 | else if (green == 3) { 260 | setColor = lerp(setColor, half4(1, 1, 1, 1), texcol.z*_SwarmRoom); 261 | } 262 | setColor = lerp(setColor, tex2D(_PalTex, float2(1.5 / 32.0, 7.5 / 8.0)), clamp(red*(red < 10 ? lerp(notFloorDark, 1, 0.5) : 1)*_fogAmount / 30.0, 0, 1)); 263 | 264 | //if (red >= 5) { 265 | // checkMaskOut = true; 266 | //} 267 | } 268 | 269 | if (red >= 5) { 270 | float4 grabTexCol = tex2D(_GrabTexture, float2(screenPos.x, 1-screenPos.y)); 271 | if (grabTexCol.x > 1.0 / 255.0 || grabTexCol.y != 0.0 || grabTexCol.z != 0.0) { 272 | setColor = grabTexCol; 273 | setColor.w = 0; 274 | red = 5; 275 | effectCol = 0; 276 | } 277 | } 278 | 279 | if (checkMaskOut) { 280 | float4 grabTexCol = tex2D(_GrabTexture, float2(screenPos.x, 1-screenPos.y)); 281 | if (grabTexCol.x > 1.0 / 255.0 || grabTexCol.y != 0.0 || grabTexCol.z != 0.0) { 282 | setColor.w = 0; 283 | } 284 | } 285 | else if (green == 0 || green >= 3) { 286 | if (setColor.x == -1) { 287 | setColor = tex2D(_PalTex, float2(0.5 / 32.0, 7.5 / 8)); 288 | effectCol = pow(1 - i.uv.y, 3) * 0.5 * i.clr.w; 289 | } 290 | 291 | effectCol = lerp(effectCol, 1, (red / 30.0)*0.1*i.clr.w); 292 | 293 | if (effectCol < 0.75) 294 | setColor = lerp(setColor, lerp(tex2D(_PalTex, float2(31.5 / 32.0, 5.5 / 8.0)), tex2D(_PalTex, float2(30.5 / 32.0, 4.5 / 8.0)), red / 30.0), effectCol / 0.75); 295 | else 296 | setColor = lerp(lerp(tex2D(_PalTex, float2(31.5 / 32.0, 5.5 / 8.0)), tex2D(_PalTex, float2(30.5 / 32.0, 4.5 / 8.0)), red / 30.0), half4(1, 1, 1, 1), effectCol - 0.75); 297 | } 298 | 299 | // Color Adjustment params 300 | setColor.rgb *= _darkness; 301 | setColor.rgb = ((setColor.rgb - 0.5) * _contrast) + 0.5; 302 | float greyscale = dot(setColor.rgb, float3(.222, .707, .071)); // Convert to greyscale numbers with magic luminance numbers 303 | setColor.rgb = lerp(float3(greyscale, greyscale, greyscale), setColor.rgb, _saturation); 304 | setColor.rgb = applyHue(setColor.rgb, _hue); 305 | setColor.rgb += _brightness; 306 | return setColor; 307 | } 308 | ENDCG 309 | 310 | 311 | 312 | } 313 | } 314 | } 315 | } 316 | -------------------------------------------------------------------------------- /SourceCode/TypeCameras/PositionTypeCamera.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace SBCameraScroll; 3 | 4 | public class PositionTypeCamera : IAmATypeCamera { 5 | // 6 | // parameters 7 | // 8 | 9 | public static float camera_box_x = 40f; 10 | public static float camera_box_y = 40f; 11 | public static float offset_speed_multiplier = 0.2f; 12 | 13 | private readonly RoomCamera _room_camera; 14 | private readonly RoomCameraFields _room_camera_fields; 15 | 16 | // 17 | // variables 18 | // 19 | 20 | public EntityID? follow_abstract_creature_id = null; 21 | public Vector2 camera_offset = new(); 22 | 23 | // 24 | // 25 | // 26 | 27 | public PositionTypeCamera(RoomCamera room_camera, RoomCameraFields room_camera_fields) { 28 | _room_camera = room_camera; 29 | _room_camera_fields = room_camera_fields; 30 | } 31 | 32 | // 33 | // public 34 | // 35 | 36 | private void Move_Camera_Towards_Target(Vector2 target_position, Vector2 at_border_difference) { 37 | // slow down at border; otherwise it would move at full speed and then suddenly 38 | // stop completely; 39 | float camera_box_minus_border_x = Mathf.Max(0.0f, camera_box_x - Mathf.Abs(at_border_difference.x)); 40 | float camera_box_minus_border_y = Mathf.Max(0.0f, camera_box_y - Mathf.Abs(at_border_difference.y)); 41 | 42 | float distance_x = Mathf.Abs(target_position.x - _room_camera.lastPos.x); 43 | float distance_y = Mathf.Abs(target_position.y - _room_camera.lastPos.y); 44 | 45 | if (distance_x > camera_box_minus_border_x) { 46 | // the goal is to reach camera_box_minus_border_x-close to target_position.x 47 | // the result is the same as: 48 | // roomCamera.pos.x = Mathf.Lerp(roomCamera.lastPos.x, camera_box_minus_border_x-close to target_position.x, t = smoothing_factor); 49 | // 50 | // the tick can fix a bug; every little difference to target_position can mean 51 | // that your monitor screen is a pixel off; this is only noticeable when using 52 | // split screen; the tick makes sure that the last 0.000-whatever difference 53 | // gets reduced to zero and does not leave a line of black pixels; 54 | _room_camera.pos.x = Custom.LerpAndTick(_room_camera.lastPos.x, target_position.x, smoothing_factor * (distance_x - camera_box_minus_border_x) / distance_x, 0.01f); 55 | float speed_difference_x = 0.8f - Mathf.Abs(_room_camera.pos.x - _room_camera.lastPos.x); 56 | 57 | if (speed_difference_x > 0f) { 58 | if (at_border_difference.x == 0f) { 59 | // stop when moving too slow; downside is that target_position might not 60 | // be reached exactly; depending on smoothing_factor this can be a couple 61 | // of pixels far away from target_position; 62 | _room_camera.pos.x = _room_camera.lastPos.x; 63 | } else { 64 | // exclude when reaching the border; changing zoom in split screen can mean that 65 | // you move from outside the border to target_position; otherwise, in that case 66 | // it would leave a small black border; 67 | // 68 | // still, let's make sure that the camera does not move too slowly; 69 | _room_camera.pos.x = Custom.LerpAndTick(_room_camera.pos.x, target_position.x, 0f, speed_difference_x); 70 | } 71 | } 72 | } else { 73 | _room_camera.pos.x = _room_camera.lastPos.x; 74 | } 75 | 76 | if (distance_y > camera_box_minus_border_y) { 77 | _room_camera.pos.y = Custom.LerpAndTick(_room_camera.lastPos.y, target_position.y, smoothing_factor * (distance_y - camera_box_minus_border_y) / distance_y, 0.01f); 78 | float speed_difference_y = 0.8f - Mathf.Abs(_room_camera.pos.y - _room_camera.lastPos.y); 79 | 80 | if (speed_difference_y > 0f) { 81 | if (at_border_difference.y == 0f) { 82 | _room_camera.pos.y = _room_camera.lastPos.y; 83 | } else { 84 | _room_camera.pos.y = Custom.LerpAndTick(_room_camera.pos.y, target_position.y, 0f, speed_difference_y); 85 | } 86 | } 87 | } else { 88 | _room_camera.pos.y = _room_camera.lastPos.y; 89 | } 90 | } 91 | 92 | public void Move_Camera_Without_Offset() { 93 | Vector2 target_position = _room_camera_fields.on_screen_position; 94 | CheckBorders(_room_camera, ref target_position); 95 | 96 | Vector2 at_border_difference = _room_camera_fields.on_screen_position - target_position; 97 | Move_Camera_Towards_Target(target_position, at_border_difference); 98 | } 99 | 100 | public void Move_Camera_With_Offset_Using_Player_Input(in Player player) { 101 | Vector2 target_position = _room_camera_fields.on_screen_position + camera_offset; 102 | CheckBorders(_room_camera, ref target_position); 103 | 104 | Vector2 at_border_difference = _room_camera_fields.on_screen_position + camera_offset - target_position; 105 | Move_Camera_Towards_Target(target_position, at_border_difference); 106 | Update_Camera_Offset_Using_Player_Input(player, at_border_difference); 107 | } 108 | 109 | public void Move_Camera_With_Offset_Using_Position_Input() { 110 | Vector2 target_position = _room_camera_fields.on_screen_position + camera_offset; 111 | CheckBorders(_room_camera, ref target_position); 112 | 113 | Vector2 at_border_difference = _room_camera_fields.on_screen_position + camera_offset - target_position; 114 | Move_Camera_Towards_Target(target_position, at_border_difference); 115 | Update_Camera_Offset_Using_Position_Input(at_border_difference); 116 | } 117 | 118 | public void Reset() { 119 | UpdateOnScreenPosition(_room_camera); 120 | CheckBorders(_room_camera, ref _room_camera_fields.on_screen_position); // do not move past room boundaries 121 | 122 | // center camera on player 123 | _room_camera.lastPos = _room_camera_fields.on_screen_position; 124 | _room_camera.pos = _room_camera_fields.on_screen_position; 125 | follow_abstract_creature_id = _room_camera.followAbstractCreature?.ID; 126 | camera_offset = new(); 127 | } 128 | 129 | public void Update() { 130 | if (_room_camera.followAbstractCreature == null) return; 131 | if (_room_camera.room == null) return; 132 | UpdateOnScreenPosition(_room_camera); 133 | 134 | // is_in_transition 135 | if (follow_abstract_creature_id != _room_camera.followAbstractCreature.ID && _room_camera.followAbstractCreature?.realizedCreature is Creature creature) { 136 | follow_abstract_creature_id = _room_camera.followAbstractCreature.ID; 137 | _room_camera.pos = _room_camera.lastPos; // just wait this frame and resume next frame; 138 | 139 | { 140 | if (creature is Player player) { 141 | camera_offset.x = player.input[0].x * camera_box_x; 142 | camera_offset.y = player.input[0].y * camera_box_y; 143 | } 144 | } 145 | return; 146 | } 147 | 148 | if (!Option_CameraOffset) { 149 | Move_Camera_Without_Offset(); 150 | return; 151 | } 152 | 153 | // scope the variable player; otherwise I need to re-name stuff; 154 | { 155 | if (_room_camera.followAbstractCreature?.realizedObject is Player player) { 156 | // same as the other function but uses player inputs 157 | // instead of position changes in order to update 158 | // the offset; 159 | Move_Camera_With_Offset_Using_Player_Input(player); 160 | return; 161 | } 162 | } 163 | Move_Camera_With_Offset_Using_Position_Input(); 164 | } 165 | 166 | private void Update_Camera_Offset_Using_Player_Input(in Player player, Vector2 at_border_difference) { 167 | // this translated to a speed of 4 tiles per second; 168 | // this seems to work even when using Gourmand and being exhausted; 169 | float buffer = 2f; 170 | 171 | bool has_target_moved_x = Mathf.Abs(_room_camera_fields.on_screen_position.x - _room_camera_fields.last_on_screen_position.x) > buffer; 172 | bool has_target_moved_y = Mathf.Abs(_room_camera_fields.on_screen_position.y - _room_camera_fields.last_on_screen_position.y) > buffer; 173 | 174 | bool has_target_turned_around_x = has_target_moved_x && player.input[0].x != 0 && player.input[0].x == -Math.Sign(camera_offset.x) && player.input[0].x == Math.Sign(_room_camera_fields.on_screen_position.x - _room_camera_fields.last_on_screen_position.x); 175 | bool has_target_turned_around_y = has_target_moved_y && player.input[0].y != 0 && player.input[0].y == -Math.Sign(camera_offset.y) && player.input[0].y == Math.Sign(_room_camera_fields.on_screen_position.y - _room_camera_fields.last_on_screen_position.y); 176 | 177 | bool has_target_and_camera_moved_x = player.input[0].x != 0 && _room_camera.pos.x != _room_camera.lastPos.x; 178 | bool has_target_and_camera_moved_y = player.input[0].y != 0 && _room_camera.pos.y != _room_camera.lastPos.y; 179 | 180 | Update_Camera_Offset_XY(ref camera_offset.x, at_border_difference.x, camera_box_x, has_target_turned_around_x, has_target_and_camera_moved_x); 181 | Update_Camera_Offset_XY(ref camera_offset.y, at_border_difference.y, camera_box_y, has_target_turned_around_y, has_target_and_camera_moved_y); 182 | } 183 | 184 | private void Update_Camera_Offset_Using_Position_Input(Vector2 at_border_difference) { 185 | float buffer = 2f; 186 | 187 | bool has_target_moved_x = Mathf.Abs(_room_camera_fields.on_screen_position.x - _room_camera_fields.last_on_screen_position.x) > buffer; 188 | bool has_target_moved_y = Mathf.Abs(_room_camera_fields.on_screen_position.y - _room_camera_fields.last_on_screen_position.y) > buffer; 189 | 190 | bool has_target_turned_around_x = has_target_moved_x && Math.Sign(_room_camera_fields.on_screen_position.x - _room_camera_fields.last_on_screen_position.x) == -Math.Sign(camera_offset.x); 191 | bool has_target_turned_around_y = has_target_moved_y && Math.Sign(_room_camera_fields.on_screen_position.y - _room_camera_fields.last_on_screen_position.y) == -Math.Sign(camera_offset.y); 192 | 193 | bool has_target_and_camera_moved_x = has_target_moved_x && _room_camera.pos.x != _room_camera.lastPos.x; 194 | bool has_target_and_camera_moved_y = has_target_moved_y && _room_camera.pos.y != _room_camera.lastPos.y; 195 | 196 | Update_Camera_Offset_XY(ref camera_offset.x, at_border_difference.x, camera_box_x, has_target_turned_around_x, has_target_and_camera_moved_x); 197 | Update_Camera_Offset_XY(ref camera_offset.y, at_border_difference.y, camera_box_y, has_target_turned_around_y, has_target_and_camera_moved_y); 198 | } 199 | 200 | // probably not worth the refactor; 201 | // plus now these things are coupled; 202 | private void Update_Camera_Offset_XY(ref float camera_offset, float at_border_difference, float camera_box, bool has_target_turned_around, bool has_target_and_camera_moved) { 203 | bool is_at_border = Mathf.Abs(at_border_difference) > 0.1f; 204 | float buffer = 10f; 205 | 206 | // if I clamp using 2f * camera_box then there are situations where 207 | // the camera moves instantly when turning; 208 | // this can be annoying sometimes when you for example trying to pipe juke; 209 | float maximum_offset = 1.5f * camera_box; 210 | 211 | if (is_at_border) { 212 | if (at_border_difference > camera_box + buffer) { 213 | camera_offset = Mathf.Clamp(camera_offset - at_border_difference + camera_box + buffer, -maximum_offset, maximum_offset); 214 | } else if (at_border_difference < -camera_box - buffer) { 215 | camera_offset = Mathf.Clamp(camera_offset - at_border_difference - camera_box - buffer, -maximum_offset, maximum_offset); 216 | } 217 | return; 218 | } 219 | 220 | if (has_target_turned_around) { 221 | camera_offset = 0f; 222 | return; 223 | } 224 | 225 | if (!has_target_and_camera_moved) return; 226 | camera_offset = Mathf.Clamp(camera_offset + offset_speed_multiplier * (_room_camera_fields.on_screen_position.x - _room_camera_fields.last_on_screen_position.x), -maximum_offset, maximum_offset); 227 | } 228 | } 229 | --------------------------------------------------------------------------------