Compare commits

..

7 Commits

4 changed files with 55 additions and 53 deletions

View File

@@ -5,7 +5,11 @@
<h3>Subscribe to my future posts</h3>
</div>
</div>
<form action="https://list.hallada.net/subscribe" method="POST" accept-charset="utf-8">
<form
action="https://list.hallada.net/subscribe"
method="POST"
accept-charset="utf-8"
>
<div class="row clearfix">
<div class="column half">
<label for="name">Name (optional)</label><br />
@@ -17,20 +21,22 @@
</div>
</div>
<div class="row clearfix">
<div style="display:none;">
<div style="display: none">
<label for="hp">HP</label><br />
<input type="text" name="hp" id="hp" />
</div>
<input type="hidden" name="list" value="Q7JrUBzeCeftZqwDtxsQ9w" />
<input type="hidden" name="list" value="aJAuaNkgCYdnWrea0qtjHA" />
<input type="hidden" name="subform" value="yes" />
<div class="column half">
<input type="submit" name="submit" id="submit" value="Submit" />
</div>
<div class="column half">
<span class="form-rss">Or subscribe to my <a href="/feed.xml">RSS feed</a></span>
<span class="form-rss"
>Or subscribe to my <a href="/feed.xml">RSS feed</a></span
>
</div>
</div>
</form>
<div class="row clearfix">
</div>
<div class="row clearfix"></div>
</div>
</div>

View File

@@ -14,7 +14,4 @@ layout: default
</div>
</div>
{% include comments.html %}
<!-- disabling until I fix the mail form -->
<!-- {% include mail-form.html %} -->
{% include comments.html %} {% include mail-form.html %}

View File

@@ -2,7 +2,6 @@
title: "Row Your Boat: How I made a boat physics simulation inside Oblivion Remastered"
layout: post
image: /img/blog/rowyourboat.jpg
hidden: true
---
If creativity is borne out of constraints, creating mods for games must be one of the most creative things you can do as a programmer. Its just so fun to hack a game engine to do something it was never supposed to do.
@@ -15,7 +14,7 @@ This blog post goes into depth describing a mod I made for the game [Oblivion Re
All of the scripts included in the mod are on my [GitHub here](https://github.com/thallada/RowYourBoat).
## The Game Release
### The Game Release
The Elder Scrolls series from [Bethesda Softworks](https://bethesda.net/) is probably the most modded series of games ever. One of [my previous projects involved indexing and mapping hundreds of thousands of mods for Skyrim](https://www.hallada.net/2022/10/05/modmapper-putting-every-skyrim-mod-on-a-map-with-rust.html) (the latest game in the series), so I know. A major reason why modding this series is so popular is because Bethesda releases the editor tools they used to create the games to the public for free. These tools lower the barrier of entry to modding and encourages complete beginners to try their hand at creating mods. I credit modding [Elder Scrolls IV: Oblivion](https://elderscrolls.bethesda.net/en/oblivion) when I was a teenager with originally getting me interested in programming. The tool they released for the classic 2006 Oblivion is called the [Construction Set](https://en.uesp.net/wiki/Oblivion_Mod:Construction_Set). It gave modders the ability to create new items, quests, creatures, and modify pretty much anything else in the game engine Oblivion was built off of: [Gamebryo](http://www.gamebryo.com/).
@@ -27,13 +26,13 @@ The introduction of Unreal Engine was both a blessing and a curse for modding. O
Luckily, a lot of the modding tools that were used for the original game worked with the remaster after some tweaks. [The community discovered that you could use the original Construction Set](https://discord.com/channels/1364356029932109976/1370869854193582212) (along with [the fantastic Construction Set Extender](https://www.nexusmods.com/oblivion/mods/36370)) to create plugins that would load in Oblivion Remastered. New beta versions of [xEdit](https://github.com/TES5Edit/TES5Edit) were released to support Oblivion Remastered (a GUI program for viewing and editing data inside mod plugins). A new version of [Oblivion Script Extender for Oblivion Remastered (OBSE64)](https://github.com/ianpatt/obse64) was released (adds new scripting functions through reverse-engineering of the game engine). It was starting to look like it would be possible to create some truly complex mods in the remaster.
## The Idea
### The Idea
![Screenshot in Oblivion Remastered from the Imperial City Waterfront overlooking the Lake Rumare](/img/blog/waterfront-rumare.jpg)
In my own play-through of the game, I had just purchased the [Imperial City Waterfront shack](https://en.uesp.net/wiki/Oblivion:Shack_for_Sale) as my characters first home, and I was wandering around the waterfront right outside the shack and looking out over at the far shoreline of the [Lake Rumare](https://en.uesp.net/wiki/Oblivion:Lake_Rumare). I suddenly had the thought: wouldnt it be great to row a boat over there? So much of [Cyrodiil](https://en.uesp.net/wiki/Oblivion:Cyrodiil), the province Oblivion is set in, is carved by rivers and lakes, and its bordered on two ends by ocean. All of this space on the map is only accessible through swimming, which is the slowest and most cumbersome way to travel in the game (its also slow on a horse, which is the only “vehicle” in the game). Why _shouldnt_ the player be able to take any one of those boats that litter the shorelines and row it anywhere?
## Researching the Original Boat Mod
### Researching the Original Boat Mod
In fact, I did remember downloading some sort of controllable boat mod for Oblivion way back in the day. A quick [search on old Oblivion Nexus Mods reveals a bunch of mods that allow the player to pilot boats](https://www.nexusmods.com/games/oblivion/mods?keyword=boat&sort=endorsements), so I knew it should be possible in theory. Out of all of these, [Jason1s Pilotable Pirate Ship](https://www.nexusmods.com/oblivion/mods/3575) stood out to me. Impressively, the mod was published just a month after the initial release of the game in 2006. A lot of the other boat mods created later (reasonably) depended on and used [OBSE](https://obse.silverlock.org/) functions for their functionality, but this mod used only the games built-in scripting language. OBSE64 for Oblivion Remastered isnt yet far enough along yet to provide the same fancy functions those other mods used. So, I decided to look into the source files of Jason1s mod to find out how he made a controllable boat with the same original limited scripting language I had to deal with.
@@ -126,7 +125,7 @@ This system isnt perfect. The mods readme mentions that “Collision detec
My initial plan was to simply port Jason1s mod to Oblivion Remastered. However, I quickly realized that too many things were changed in the game engine to make Jason1s solution feasible in the remaster.
## Getting Meshes to Appear in the Game
### Getting Meshes to Appear in the Game
One of the biggest hurdles of the modding the remaster was just getting items you added in a plugin to actually appear in the game. The [ESP files](https://en.uesp.net/wiki/Oblivion_Mod:Plugins) created by the Construction Set allowed you to define new objects and their position in the game, but this was only in the old Gamebryo side of the game engine. There was a disconnect between that and the Unreal side of the game engine that prevented these objects from showing up in-game.
@@ -134,7 +133,7 @@ Luckily, [Godschildgaming](https://next.nexusmods.com/profile/Godschildgaming?ga
Any Oblivion Remastered mod that wanted to add new objects into the world would just need to add a dependency on UE4SS TesSyncMapInjector and create an INI or JSON config file that told TesSyncMapInjector what Unreal Engine model asset to use for each ESP object (referenced using their [Formids](https://en.uesp.net/wiki/Oblivion_Mod:Formid)).
## Moving the Boat
### Moving the Boat
In Jason1s Pilotable Pirate Ship mod, the player starts moving the boat by clicking on hull or wheel of the ship (“activating”) which opens a [MessageBox](https://cs.uesp.net/wiki/MessageBox_Tutorial) with options for moving forward, backward, or stopping (“drop anchor”). The first thing I did was try to replicate this with a rowboat model in the game.
@@ -146,7 +145,7 @@ I found out later that I was lucky in having attempted to try to move an activat
I was expecting to run into the problem Jason1 ran into with players clipping through the boat mesh and falling into the water while the boat was moving, but it turns out Unreal Engine actually handles this better! It had no problems with moving the player and keeping them on the deck while the boat was moving.
## Turning the Boat
### Turning the Boat
The other critical part of moving the boat is turning it so the player can dictate _where_ the boat is moving. Jason1s mod achieved this by locking the boat angle to the players view angle. So for example, when the player turned to look right, the boat would follow and turn right.
@@ -174,16 +173,16 @@ This method employs multiple techniques like reducing the domain of the angles t
With the ability to calculate sine and cosine, I could now calculate the next X and Y position of the boat given the current speed and the current angle of the boat:
```
set BoatX to BoatX + (sin _ FrameBoatVelocity)
set BoatY to BoatY + (cos _ FrameBoatVelocity)
set BoatX to BoatX + (sin * FrameBoatVelocity)
set BoatY to BoatY + (cos * FrameBoatVelocity)
...
BoatRef.SetPos x, BoatX
BoatRef.SetPos y, BoatY
```
To actually change the angle of the boat depending on the player look angle, I developed a similar solution to Jason1s. Except, instead of locking the boat angle directly to player angle, I kept them independent and instead _gradually_ modified the boat angle towards the player angle every frame. This made the turning feel much more natural and gave the rowboat realistic weight. The rate of turning also slows down as the boat speed slows down, which feels more natural.
To actually change the angle of the boat depending on the player look angle, I developed a similar solution to Jason1s. Except, instead of locking the boat angle directly to player angle, I kept them independent and instead _gradually_ modified the boat angle towards the player angle every frame. This made the turning feel much more natural and gave the rowboat realistic weight. The rate of turning also slows down as the boat speed slows down.
I also added a dead-zone a few degrees out from either side of the center line of the boat so if the player moves slightly it doesnt cause the whole boat to move. This made turning the boat much more intentional and avoided the boat from weaving too much side to side when the player is just trying to go forward.
I also added a dead-zone a few degrees out from either side of the center line of the boat so if the player moves slightly it doesnt cause the whole boat to move. This made turning the boat much more intentional and avoided the boat weaving too much side to side when the player was just attempting to go forward.
The turn rate also decays. So if the player stops turning the boat by looking directly ahead, the boat will naturally slow turning until it stops turning.
@@ -194,7 +193,7 @@ The turn rate also decays. So if the player stops turning the boat by looking di
</video>
</p>
## Detecting Collision
### Detecting Collision
Since the meshes are handled by Unreal Engine in Oblivion Remastered, I didnt think the same method Jason1 used in his original mod would work for the remaster. I also wanted a better collision detection system since it sounded like there was a lot of issues with the method of using an invisible collision plane below the player.
@@ -234,7 +233,7 @@ I was concerned that moving an NPC every frame would cause a big performance hit
So thats how I detect collision my mod: hanging a tiny invisible, mute, dumb vampire off the front of the boat until it bashes into something 😉.
## Rowing the Boat
### Rowing the Boat
While moving the boat automatically through the MessageBox menu was convenient, it was also cumbersome and awkward to interact with. I wanted to create a way to really make the player feel like they are rowing the boat for realism and ✨_immersion_✨.
@@ -269,9 +268,9 @@ I initially went with the [Darkness](https://en.uesp.net/wiki/Oblivion:Darkness)
</video>
</p>
## Realistic Movement
### Realistic Movement
When the Row spell is cast by the player while they are on the boat, it doesnt immediately shoot the boat forward at its maximum speed. Instead, the Row spell cast starts a timer where, for a short time period, it adds a small amount of “force” to the boats current velocity every game frame. This is to simulate the effect of oars pushing through the water. After constantly rowing for a while, velocity will accumulate until the boat reaches its maximum velocity.
When the Row spell is cast by the player while they are on the boat, it doesnt immediately shoot the boat forward at its maximum speed. Instead, the Row spell cast starts a timer where, for a short time period, it adds a small amount of “force” to the boats current velocity every game frame. This is to simulate the effect of oars pushing through the water. After constantly rowing for a while, velocity will accumulate until the boat reaches its maximum velocity.
Since this is an effect that applies every frame, I needed to account for players having different frame rates or variations in the frame rate. It wouldnt make any sense if the boat was faster in lower graphics settings, or slower if the player entered an area with a lower frame rate. Infamously, Oblivion has this issue with its Havok physics engine. Its tied to the games frame rate which often [causes bugs like objects erratically flying off shelves when the player enters an interior under high frame-rates](https://www.reddit.com/r/oblivion/comments/512ut0/is_fps_tied_to_physics_in_this_game/).
@@ -323,7 +322,7 @@ if (Rowing == 0 && AutoRowing == 0 && BoatMoving == 2)
Technically, my quest script doesnt run every game frame though. The special quest variable [`fQuestDelayTime`](https://cs.uesp.net/wiki/FQuestDelayTime) configures how often the script is run while the game is running. To save CPU resources, I try to keep this to a high value when the player is not near the boat, but once they start moving the boat I ramp it down to a value that would run the script at roughly 60 times per second.
## Dragging the Boat
### Dragging the Boat
The largest and most prominent river in the game: the [Niben River](https://en.uesp.net/wiki/Oblivion:Niben_River) is actually [blocked by the city Layawiin](https://en.uesp.net/wiki/Oblivion:Niben_River#Lower_Niben) in the game, which makes it impossible to row the boat all the way on the river that goes from the Imperial City into the [Topal Bay](https://en.uesp.net/wiki/Oblivion:Topal_Bay) at the bottom of the map. I knew I would need to develop an alternative way to move the boat for this reason when I set out to make this mod.
@@ -333,17 +332,17 @@ After perfecting the movement of the boat over water, I already had the necessar
At this point in the project, I was heavily using [Claude](https://claude.ai/) to help me out with the script. To be honest, as a non-game developer, a lot of the 3D math involved in this project was starting to get a bit over my head. But, Claude was an amazing tool at breaking it down for me in a way I could understand and served as a great super-powered [rubber duck](https://en.wikipedia.org/wiki/Rubber_duck_debugging) for debugging issues.
At some point while developing the boat dragging code with Claude, I had the great idea to suggest it create an [artifact](https://www.anthropic.com/news/build-artifacts?subjects=announcements) by converting the OBScript code we was working on to the equivalent in JavaScript and display an interactable 2D visualization of the dragging simulation on a HTML canvas. This was **super** helpful in debugging a ton of issues with the dragging code because it tightened the feedback loop between making a change and then testing it out to see if it worked in the visualization. I spent a lot of time waiting to Oblivion Remastered to start up and load saves while working on this mod, so this was huge. I also told Claude to include lots of sliders for all the different variables in the dragging simulation so I could quickly tweak with them within the visualization and get the feel of the dragging really refined without even needing to load up the game.
At some point while developing the boat dragging code with Claude, I had the great idea to suggest it create an [artifact](https://www.anthropic.com/news/build-artifacts?subjects=announcements) by converting the OBScript code I was working on to the equivalent in JavaScript and display an interactable 2D visualization of the dragging simulation on a HTML canvas. This was **super** helpful in debugging a ton of issues with the dragging code because it tightened the feedback loop between making a change and then testing it out to see if it worked in the visualization. I spent a lot of time waiting to Oblivion Remastered to start up and load saves while working on this mod, so this was huge. I also told Claude to include lots of sliders for all the different variables in the dragging simulation so I could quickly tweak with them within the visualization and get the feel of the dragging really refined without even needing to load up the game.
[![Screenshot of a Claude artifact web page with the title “Oblivion Boat Dragging Visualization” with a canvas displaying crude shapes representing a boat and rope and a bunch of “Dragging Parameters” slider inputs below](/img/blog/rowyourboat-dragging-visualization.jpg)](https://claude.ai/public/artifacts/23380c6b-c9a4-430d-bd86-781ae588739f)
And, now that I have this artifact, it serves as great documentation for how the dragging code works! [Try it out for yourself here](https://claude.ai/public/artifacts/23380c6b-c9a4-430d-bd86-781ae588739f).
I will certainly be using LLMs to create visualizations of tricky simulations in the future. This is the sort of thing where I think AI could truly help 10x the speed and quality of code projects. To do this in the pre-LLMs days would have taken hours. Enough time that it just wouldn't have felt worth it. But now that I can have an LLM spit it out in seconds, it would be stupid to not do it and reap the benefits of it.
I will certainly be using LLMs to create visualizations of tricky simulations in the future. This is the sort of thing where I think AI could truly help 10x the speed and quality of code projects. To do this in the pre-LLMs days would have taken hours. Enough time that it just wouldn't have felt worth it. But now that I can have an LLM spit it out in seconds, it would be dumb not to do it and reap the benefits of it.
The dragging code tries to simulate the player dragging the boat as if they were pulling a rope attached to the center of the boat. This allows the player to walk freely around the boat without it moving as long as they dont make the rope taut by walking more than the ropes length away from the center of the boat (the white circle in the visualization). Once they do, it will pull the boat with a force relative to how far away the player moved. The boat itself has friction with the ground which moderates this effect, since I wanted the dragging effect to feel slow and less practical than rowing it on water.
The boat also turns to face the bow towards the player. This makes it appear like the player is dragging the boat from its bow. The turning effect works very similarly to how the turning works on water with gradual ramp up and decay when the angle of difference enters the deadzone.
The boat also turns to face the bow towards the player. This makes it appear like the player is dragging the boat from its bow. The turning effect works very similarly to how the turning works on water with gradual ramp up and decay when the angle of difference enters the deadzone.
One thing the visualization does not show is how the boat behaves when dragged up or down hills (since it is only a 2D visualization). I wanted the pitch of the boat to change so that when the player drags it up a hill it pitches up to follow the slope of the terrain, and when they drag it downhill it would pitch down. Otherwise, the boat stuck out awkwardly horizontally from the side of hills while you were dragging it. It just looked unrealistic.
@@ -357,7 +356,7 @@ While dragging the boat, the player gains 200 pounds of encumbrance. This is to
The encumbrance is achieved by adding a special “Rowboat” item to the players inventory. The item is scripted so if it is dropped from the players inventory then the dragging stops. It also has the same rowboat model assigned to it through UE4SS TesSyncMapInjector so it even looks like a rowboat in the players inventory preview.
## Summoning the Boat
### Summoning the Boat
One of the first mods I downloaded for Oblivion Remastered was [PushTheWinButton](https://next.nexusmods.com/profile/PushTheWinButton?gameId=7587)s excellent [Horse Whistle - Summon and Follow](https://www.nexusmods.com/oblivionremastered/mods/153) . Theres a reason pretty much every game these days that has horse mounts includes some sort of “whistle” mechanic that allows the player to summon their horse to their position immediately. While not exactly realistic, its just one of those things that smooths over gameplay so its not such a chore just to get playing.
@@ -371,7 +370,7 @@ The difference between these two options is that “Summon Boat” tries to plac
I also found that I needed to disable the boat and then re-enable it after moving it, otherwise sometimes the boat would weirdly not have any collision so the player could walk right through it and it would not be activatable.
## Rocking the Boat
### Rocking the Boat
Inspired by the classic Oblivion mod [QQuix - Rock rock rock your ship](https://www.nexusmods.com/oblivion/mods/29649), I wanted to add even more realism to the mod by adding a gentle rocking animation to the boat while it is in the water.
@@ -386,19 +385,19 @@ Combining three random waves instead of a single makes the rocking more complex
The boat pitches by combining the primary and secondary waves:
```
set TargetRockPitchOffset to RockAmplitudePitch _ (rockSin _ 0.8 + rockSin2 \* 0.2)
set TargetRockPitchOffset to RockAmplitudePitch * (rockSin * 0.8 + rockSin2 * 0.2)
```
And rolls side-to-side by using a cosine function to create a 90-degree phase offset off the secondary and tertiary waves:
```
set TargetRockRollOffset to RockAmplitudeRoll _ (rockCos3 _ 0.7 + rockSin2 \* 0.3)
set TargetRockRollOffset to RockAmplitudeRoll * (rockCos3 * 0.7 + rockSin2 * 0.3)
```
And bobs up and down slightly by combining the primary and secondary waves:
```
set TargetRockZOffset to RockAmplitudeZ _ (rockSin _ 0.7 + rockSin2 \* 0.3 + RockRandomPhase)
set TargetRockZOffset to RockAmplitudeZ * (rockSin * 0.7 + rockSin2 * 0.3 + RockRandomPhase)
```
This all combines to create a fairly convincing boat rocking motion in the water:
@@ -414,9 +413,9 @@ To increase the realism, the rocking motion gets amplified by how fast the boat
```
; Increase rocking amplitude based on speed
set TargetRockZOffset to TargetRockZOffset _ (1 + (AbsoluteBoatVelocity / BoatMaxVelocity) _ RockSpeedFactor)
set TargetRockPitchOffset to TargetRockPitchOffset _ (1 + (AbsoluteBoatVelocity / BoatMaxVelocity) _ RockSpeedFactor _ 1.5)
set TargetRockRollOffset to TargetRockRollOffset _ (1 + (AbsoluteBoatVelocity / BoatMaxVelocity) _ RockSpeedFactor _ 0.7)
set TargetRockZOffset to TargetRockZOffset * (1 + (AbsoluteBoatVelocity / BoatMaxVelocity) * RockSpeedFactor)
set TargetRockPitchOffset to TargetRockPitchOffset * (1 + (AbsoluteBoatVelocity / BoatMaxVelocity) * RockSpeedFactor * 1.5)
set TargetRockRollOffset to TargetRockRollOffset * (1 + (AbsoluteBoatVelocity / BoatMaxVelocity) * RockSpeedFactor * 0.7)
```
And the rocking motion also gets amplified by bad weather by querying the wind speed with [`GetWindSpeed`](https://cs.uesp.net/wiki/GetWindSpeed) and adjusting the motion accordingly:
@@ -455,9 +454,9 @@ set PlayerRelativeY to PlayerY - BoatY
; Forward vector (bow direction): sin(BoatAngle), cos(BoatAngle)
; Right vector (starboard direction): cos(BoatAngle), -sin(BoatAngle)
; PlayerLocalY: positive = toward bow, negative = toward stern
set PlayerLocalY to PlayerRelativeX _ sin + PlayerRelativeY _ cos
set PlayerLocalY to PlayerRelativeX * sin + PlayerRelativeY * cos
; PlayerLocalX: positive = toward starboard, negative = toward port
set PlayerLocalX to PlayerRelativeX _ cos - PlayerRelativeY _ sin
set PlayerLocalX to PlayerRelativeX * cos - PlayerRelativeY * sin
set PlayerLocalZ to PlayerZ - BoatZWithRock
```
@@ -465,11 +464,11 @@ Then I calculate a weight effect to apply to the pitch and roll that diminishes
```
; Calculate distance from boat center for falloff effect
set PlayerDistanceFromCenter to PlayerRelativeX _ PlayerRelativeX + PlayerRelativeY _ PlayerRelativeY
set PlayerDistanceFromCenter to PlayerRelativeX * PlayerRelativeX + PlayerRelativeY * PlayerRelativeY
; Newton's method square root approximation (2 iterations)
set PlayerDistanceFromCenter to PlayerWeightMaxDistanceForward ; Initial guess
set PlayerDistanceFromCenter to (PlayerDistanceFromCenter + ((PlayerRelativeX _ PlayerRelativeX + PlayerRelativeY _ PlayerRelativeY) / PlayerDistanceFromCenter)) / 2
set PlayerDistanceFromCenter to (PlayerDistanceFromCenter + ((PlayerRelativeX _ PlayerRelativeX + PlayerRelativeY _ PlayerRelativeY) / PlayerDistanceFromCenter)) / 2
set PlayerDistanceFromCenter to (PlayerDistanceFromCenter + ((PlayerRelativeX * PlayerRelativeX + PlayerRelativeY * PlayerRelativeY) / PlayerDistanceFromCenter)) / 2
set PlayerDistanceFromCenter to (PlayerDistanceFromCenter + ((PlayerRelativeX * PlayerRelativeX + PlayerRelativeY * PlayerRelativeY) / PlayerDistanceFromCenter)) / 2
```
Using the distance from center I can then calculate an influence factor to apply to the pitch and roll on top of the randomized environmental pitch and roll (by adding these offsets):
@@ -500,7 +499,7 @@ set PlayerWeightRollOffset to PlayerWeightRollOffset + ((TargetPlayerWeightRollO
All of the rocking motion stops if the boat collides with land or if the player moves far enough away from the boat to save unnecessary processing. For players that want to minimize the performance impact, I also added a setting in the MessageBox menu to turn off the rocking animation.
## Boat Upgrades
### Boat Upgrades
The rowboat itself is purchasable from [Sergius Verus](https://en.uesp.net/wiki/Oblivion:Sergius_Verus) at the [Three Brothers Trade Goods](https://en.uesp.net/wiki/Oblivion:Three_Brothers_Trade_Goods) in the Market District of the Imperial City. This was implemented similarly to how [buying houses in the game works](https://en.uesp.net/wiki/Oblivion:Buy_a_house_in_the_Imperial_City). You purchase a deed document from the trader which has a script attached to it with an [`OnAdd`](https://cs.uesp.net/wiki/OnAdd) block that triggers when it is added to the players inventory which then changes the owner of the house to the player and gives them the key. In the case of my rowboat, it just flips a variable in my script which makes the rowboat operable by the player and removes the for-sale sign next to the boat where it is docked in the [Waterfront District](https://en.uesp.net/wiki/Oblivion:Waterfront_District).
@@ -525,30 +524,30 @@ Eventually I realized that the order that yaw, roll, and pitch were applied matt
```
; yaw
set RYB.TempX to RYB.SeatSideOffset _ RYB.cos + RYB.SeatForwardOffset _ RYB.sin
set RYB.TempY to RYB.SeatForwardOffset _ RYB.cos - RYB.SeatSideOffset _ RYB.sin
set RYB.TempX to RYB.SeatSideOffset * RYB.cos + RYB.SeatForwardOffset * RYB.sin
set RYB.TempY to RYB.SeatForwardOffset * RYB.cos - RYB.SeatSideOffset * RYB.sin
set RYB.TempZ to RYB.SeatZOffset
; roll
set RYB.OrigX to RYB.TempX
set RYB.OrigZ to RYB.TempZ
set RYB.TempX to RYB.OrigX _ RYB.CosRoll - RYB.OrigZ _ RYB.SinRoll
set RYB.TempZ to RYB.OrigX _ RYB.SinRoll + RYB.OrigZ _ RYB.CosRoll
set RYB.TempX to RYB.OrigX * RYB.CosRoll - RYB.OrigZ * RYB.SinRoll
set RYB.TempZ to RYB.OrigX * RYB.SinRoll + RYB.OrigZ * RYB.CosRoll
; pitch
set RYB.OrigY to RYB.TempY
set RYB.OrigZ to RYB.TempZ
set RYB.TempY to RYB.OrigY _ RYB.CosPitch + RYB.OrigZ _ RYB.SinPitch
set RYB.TempZ to -RYB.OrigY _ RYB.SinPitch + RYB.OrigZ _ RYB.CosPitch
set RYB.TempY to RYB.OrigY * RYB.CosPitch + RYB.OrigZ * RYB.SinPitch
set RYB.TempZ to -RYB.OrigY * RYB.SinPitch + RYB.OrigZ * RYB.CosPitch
; to world coords
set RYB.SeatX to RYB.BoatX + RYB.TempX
set RYB.SeatY to RYB.BoatY + RYB.TempY
set RYB.SeatZ to RYB.BoatZ + RYB.TempZ + RYB.RockZOffset
```
## Mod Release
### Mod Release
The finally released the mod on June 4th, 2025 [on Nexus Mods](https://www.nexusmods.com/oblivionremastered/mods/4273) and [made a post on the r/oblivionmods subreddit](https://www.reddit.com/r/oblivionmods/comments/1l3pg6z/row_your_boat_usable_rowboat_mod/). It was fun seeing the response. A lot of people were excited to see a mod of this complexity released. I think they saw it as a sign that Oblivion Remastered was more mod-friendly than the doubters believed, and we would all see more sophisticated mods coming out for Oblivion Remastered soon. [Rock Paper Shotgun even featured my mod](https://www.rockpapershotgun.com/oblivion-remastered-your-own-personal-rideable-rowboat-mod-sailing-around-cyrodiil-as-magical-mariner), which was cool!
## The Mysterious Case of The Spontaneously Duplicating Rowboats
### The Mysterious Case of The Spontaneously Duplicating Rowboats
After release, I made a few updated versions that fixed various bugs that were reported by the community in the [bug tracker](https://www.nexusmods.com/oblivionremastered/mods/4273?tab=bugs). But, one bug that was _really_ stumping me was the issue where players would report that sometimes their boat would spontaneously duplicate itself rendering both boats broken and unusable.
@@ -568,9 +567,9 @@ I never truly found out the root-cause of the duplicating boats. The process I f
So, I suspect it has something to do with Unreal Engine getting Construction Set placed references mixed up with references that have been moved by scripts outside their originally placed cell, and somehow duplicating the reference in the process.
I havent gotten any reports from users that the boat duplication bug is still happening after I released a new version with the UE4SS script. I still get the occasional user reporting crashes that happen, but its hard to prove what mod in their load order is really causing the crash, and many users report my mod because they see the log messages my script writes in their UE4SS logs. Personally, I didnt experience any crashes with a bare-bones load order with just my mod and its dependencies installed.
I havent gotten any reports from users that the boat duplication bug is still happening after I released a new version with the UE4SS script. I still get the occasional user reporting crashes that happen, but its hard to prove what mod in their load order is really causing the crash, and many users report my mod because they see the log messages my script writes in their UE4SS logs. Personally, I didnt experience any crashes with a bare-bones load order with just my mod and its dependencies installed.
## Future Work
### Future Work
Unless I get infatuated with Oblivion modding again, I dont think Ill be adding anything more to the mod anytime soon. But, if I were to, I think theres a lot more I could add to improve the mod:

Binary file not shown.