Compare commits
7 Commits
a182bb1184
...
dc1211dc77
| Author | SHA1 | Date | |
|---|---|---|---|
| dc1211dc77 | |||
| 28dd5a5615 | |||
| cdc4de9c36 | |||
| 14235120c8 | |||
| cfa8480f5d | |||
| 08d5ab98da | |||
| 18558b3a5c |
@@ -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>
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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. It’s 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
|
||||
|
||||

|
||||
|
||||
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 character’s 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: wouldn’t 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 it’s 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 (it’s also slow on a horse, which is the only “vehicle” in the game). Why _shouldn’t_ 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 game’s built-in scripting language. OBSE64 for Oblivion Remastered isn’t 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 Jason1’s 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 isn’t perfect. The mod’s readme mentions that “Collision detec
|
||||
|
||||
My initial plan was to simply port Jason1’s mod to Oblivion Remastered. However, I quickly realized that too many things were changed in the game engine to make Jason1’s 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 Jason1’s 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. Jason1’s mod achieved this by locking the boat angle to the player’s 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 Jason1’s. 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 Jason1’s. 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 doesn’t 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 doesn’t 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 didn’t 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 that’s 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 doesn’t immediately shoot the boat forward at it’s 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 boat’s 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 doesn’t 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 boat’s 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 wouldn’t 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. It’s tied to the game’s 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 doesn’t 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.
|
||||
|
||||
[](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 don’t make the rope taut by walking more than the rope’s 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 it’s 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 player’s inventory. The item is scripted so if it is dropped from the player’s 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 player’s 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) . There’s 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, it’s just one of those things that smooths over gameplay so it’s 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 player’s 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 haven’t 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 it’s 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 didn’t experience any crashes with a bare-bones load order with just my mod and it’s dependencies installed.
|
||||
I haven’t 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 it’s 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 didn’t 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 don’t think I’ll be adding anything more to the mod anytime soon. But, if I were to, I think there’s a lot more I could add to improve the mod:
|
||||
|
||||
|
||||
BIN
resume.pdf
BIN
resume.pdf
Binary file not shown.
Reference in New Issue
Block a user