Fix boat duplication/disappearing, reduce collision false-positives
Needed to add a UE4SS script to fix the boat duplication issue. Disappearing issue was fixed by disabling and re-enabling when getting back to the boat. Collision false-positives was reduced by ignoring collider position when it is too far off expected X and Y positions. Still not ready for next relase, needs some update migration code.
This commit is contained in:
@@ -5,6 +5,7 @@ set RYB.ColliderOffset to 300
|
|||||||
set RYB.ColliderOffsetReverse to 350
|
set RYB.ColliderOffsetReverse to 350
|
||||||
set RYB.ColliderMoveFreq to 0.05
|
set RYB.ColliderMoveFreq to 0.05
|
||||||
set RYB.ColliderZ to 1
|
set RYB.ColliderZ to 1
|
||||||
|
set RYB.ColliderPosThreshold to 1.0
|
||||||
RYBColliderRef.SetActorAlpha 0.0
|
RYBColliderRef.SetActorAlpha 0.0
|
||||||
RYBColliderRef.SetActorRefraction 10.0
|
RYBColliderRef.SetActorRefraction 10.0
|
||||||
RYBColliderRef.AddSpell MG14JskarInvis
|
RYBColliderRef.AddSpell MG14JskarInvis
|
||||||
|
|||||||
8
ResultScripts/RYBQuestStage50DisableRefs.psc
Normal file
8
ResultScripts/RYBQuestStage50DisableRefs.psc
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
; RYB Stage 50 Disable Boat and Attachment Refs
|
||||||
|
|
||||||
|
RYBBoatRef.Disable
|
||||||
|
RYBSeatRef.Disable
|
||||||
|
RYBChestRef.Disable
|
||||||
|
RYBLampOnRef.Disable
|
||||||
|
RYBLampOffRef.Disable
|
||||||
|
RYBLadderRef.Disable
|
||||||
17
ResultScripts/RYBQuestStage51EnableRefs.psc
Normal file
17
ResultScripts/RYBQuestStage51EnableRefs.psc
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
; RYB Stage 51 Enable Boat and Attachment Refs
|
||||||
|
|
||||||
|
RYBBoatRef.Enable
|
||||||
|
RYBBoatMapMarker.Enable
|
||||||
|
RYBSeatRef.Enable
|
||||||
|
if (RYB.ChestPurchased == 1)
|
||||||
|
RYBChestRef.Enable
|
||||||
|
endif
|
||||||
|
if (RYB.LampPurchased == 1)
|
||||||
|
if (RYB.LampOn == 1)
|
||||||
|
RYBLampOnRef.Enable
|
||||||
|
endif
|
||||||
|
RYBLampOffRef.Enable
|
||||||
|
endif
|
||||||
|
if (RYB.LadderPurchased == 1 && RYB.LadderDeployed == 1)
|
||||||
|
RYBLadderRef.Enable
|
||||||
|
endif
|
||||||
BIN
RowYourBoat.esp
BIN
RowYourBoat.esp
Binary file not shown.
@@ -14,7 +14,10 @@ short Initializing
|
|||||||
short BoatMoving ; 0 = not moving, 1 = about to move this tick, 2 = in motion
|
short BoatMoving ; 0 = not moving, 1 = about to move this tick, 2 = in motion
|
||||||
short Rowing ; 0 = not rowing (timed), 1 = rowing forward (timed), 2 = rowing backward (timed)
|
short Rowing ; 0 = not rowing (timed), 1 = rowing forward (timed), 2 = rowing backward (timed)
|
||||||
short AutoRowing ; 0 = not auto rowing, 1 = auto rowing forward, 2 = auto rowing backward
|
short AutoRowing ; 0 = not auto rowing, 1 = auto rowing forward, 2 = auto rowing backward
|
||||||
short Resetting ; 1 = run positioning code once relative to player, 2 = run positioning code once relative to boat
|
; 1 = run positioning code once relative to player
|
||||||
|
; 2 = run positioning code once relative to boat
|
||||||
|
; -1 = re-enable boat and attachments, then run positioning code once relative to player
|
||||||
|
short Resetting
|
||||||
short LockHeading ; 0 = free to turn, 1 = locked to current boat heading
|
short LockHeading ; 0 = free to turn, 1 = locked to current boat heading
|
||||||
short LadderDeployed ; 0 = disabled, 1 = enabled
|
short LadderDeployed ; 0 = disabled, 1 = enabled
|
||||||
short LampOn
|
short LampOn
|
||||||
@@ -28,6 +31,7 @@ short BoatPurchased
|
|||||||
short ChestPurchased
|
short ChestPurchased
|
||||||
short LampPurchased
|
short LampPurchased
|
||||||
short LadderPurchased
|
short LadderPurchased
|
||||||
|
short PlayerNearBoat ; 0 = not near boat, 1 = near boat (within RockDistanceThreshold)
|
||||||
|
|
||||||
; Triggers
|
; Triggers
|
||||||
; Sort of like functions that any script can call by setting these to 1 which this script will handle and set back to 0
|
; Sort of like functions that any script can call by setting these to 1 which this script will handle and set back to 0
|
||||||
@@ -276,7 +280,9 @@ float LadderZOffset ; pos units from BoatZ upwards to place the ladder (default:
|
|||||||
|
|
||||||
ref Collider
|
ref Collider
|
||||||
float ColliderX
|
float ColliderX
|
||||||
|
float ColliderActualX
|
||||||
float ColliderY
|
float ColliderY
|
||||||
|
float ColliderActualY
|
||||||
float ColliderZ
|
float ColliderZ
|
||||||
float ColliderOffset ; pos units from boat center towards boat direction to place the collider
|
float ColliderOffset ; pos units from boat center towards boat direction to place the collider
|
||||||
float ColliderOffsetReverse ; pos units from boat center towards the back of the boat to place the collider
|
float ColliderOffsetReverse ; pos units from boat center towards the back of the boat to place the collider
|
||||||
@@ -285,6 +291,8 @@ float ColliderMoveTimer ; current time left to wait before moving the collider a
|
|||||||
float CollisionDetectDelay ; how long to wait in seconds at boat startup before checking for collision (default: 2)
|
float CollisionDetectDelay ; how long to wait in seconds at boat startup before checking for collision (default: 2)
|
||||||
float CollisionDetectTimer ; current time left to give for moving the boat away at startup before detecting collision
|
float CollisionDetectTimer ; current time left to give for moving the boat away at startup before detecting collision
|
||||||
short CollisionDetectZThreshold ; Z position that the collider must be above to be considered colliding with something
|
short CollisionDetectZThreshold ; Z position that the collider must be above to be considered colliding with something
|
||||||
|
; how close (in units) the collider must be to the expected position to trigger a collision (default: 1.0)
|
||||||
|
float ColliderPosThreshold
|
||||||
|
|
||||||
begin GameMode
|
begin GameMode
|
||||||
if (Initializing == 0)
|
if (Initializing == 0)
|
||||||
@@ -301,6 +309,11 @@ begin GameMode
|
|||||||
SetStage RYB 7 ; init player weight
|
SetStage RYB 7 ; init player weight
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
if (Resetting == -1)
|
||||||
|
SetStage RYB 51 ; Re-enable boat and attachment refs
|
||||||
|
set Resetting to 1
|
||||||
|
endif
|
||||||
|
|
||||||
set PlayerDistance to BoatRef.GetDistance Player
|
set PlayerDistance to BoatRef.GetDistance Player
|
||||||
if (PlayerDistance < RockDistanceThreshold)
|
if (PlayerDistance < RockDistanceThreshold)
|
||||||
if (RockingEnabled == 1)
|
if (RockingEnabled == 1)
|
||||||
@@ -308,8 +321,17 @@ begin GameMode
|
|||||||
else
|
else
|
||||||
set fQuestDelayTime to MediumUpdateRate ; medium processing rate when nearby but rocking is disabled
|
set fQuestDelayTime to MediumUpdateRate ; medium processing rate when nearby but rocking is disabled
|
||||||
endif
|
endif
|
||||||
|
if (PlayerNearBoat == 0)
|
||||||
|
; Fix bug with boat disappearing after returning to the boat's cell by disabling all refs and re-enable in
|
||||||
|
; the next frame
|
||||||
|
SetStage RYB 50 ; Disable boat and attachment refs
|
||||||
|
set Resetting to -1 ; re-enable boat and attachment refs next frame
|
||||||
|
endif
|
||||||
|
set PlayerNearBoat to 1
|
||||||
else
|
else
|
||||||
set fQuestDelayTime to LowUpdateRate ; low processing rate when far away
|
set fQuestDelayTime to LowUpdateRate ; low processing rate when far away
|
||||||
|
set PlayerNearBoat to 0
|
||||||
|
; SetStage RYB 50 ; Disable boat and attachment refs when away
|
||||||
endif
|
endif
|
||||||
|
|
||||||
if (TriggerAutoRow == 1)
|
if (TriggerAutoRow == 1)
|
||||||
@@ -508,13 +530,7 @@ begin GameMode
|
|||||||
set BoatY to PlayerY + (PlayerCos * SummonDistance)
|
set BoatY to PlayerY + (PlayerCos * SummonDistance)
|
||||||
set BoatZ to PlayerZ
|
set BoatZ to PlayerZ
|
||||||
; Disable all the refs first since they need to be disabled and enabled one frame later to appear correctly
|
; Disable all the refs first since they need to be disabled and enabled one frame later to appear correctly
|
||||||
BoatRef.Disable
|
SetStage RYB 50 ; Disable boat and attachment refs
|
||||||
BoatMarker.Disable
|
|
||||||
Seat.Disable
|
|
||||||
Chest.Disable
|
|
||||||
LampLit.Disable
|
|
||||||
LampUnlit.Disable
|
|
||||||
Ladder.Disable
|
|
||||||
BoatRef.MoveTo Player
|
BoatRef.MoveTo Player
|
||||||
BoatRef.SetPos x, BoatX
|
BoatRef.SetPos x, BoatX
|
||||||
BoatRef.SetPos y, BoatY
|
BoatRef.SetPos y, BoatY
|
||||||
@@ -535,13 +551,7 @@ begin GameMode
|
|||||||
if (BoatZ < (WaterLevelZ + 1))
|
if (BoatZ < (WaterLevelZ + 1))
|
||||||
set BoatZ to WaterLevelZ + 1
|
set BoatZ to WaterLevelZ + 1
|
||||||
endif
|
endif
|
||||||
BoatRef.Disable
|
SetStage RYB 50 ; Disable boat and attachment refs
|
||||||
BoatMarker.Disable
|
|
||||||
Seat.Disable
|
|
||||||
Chest.Disable
|
|
||||||
LampLit.Disable
|
|
||||||
LampUnlit.Disable
|
|
||||||
Ladder.Disable
|
|
||||||
BoatRef.MoveTo Player
|
BoatRef.MoveTo Player
|
||||||
BoatRef.SetPos x, BoatX
|
BoatRef.SetPos x, BoatX
|
||||||
BoatRef.SetPos y, BoatY
|
BoatRef.SetPos y, BoatY
|
||||||
@@ -554,32 +564,18 @@ begin GameMode
|
|||||||
; set SummonTimer to SummonTimerDelay ; this delay maybe not necessary
|
; set SummonTimer to SummonTimerDelay ; this delay maybe not necessary
|
||||||
elseif (Summoning == 2)
|
elseif (Summoning == 2)
|
||||||
set Summoning to 0
|
set Summoning to 0
|
||||||
BoatRef.Enable
|
SetStage RYB 51 ; Re-enable boat and attachment refs
|
||||||
BoatMarker.Enable
|
|
||||||
Seat.Enable
|
|
||||||
if (ChestPurchased == 1)
|
|
||||||
Chest.Enable
|
|
||||||
endif
|
|
||||||
if (LampPurchased == 1)
|
|
||||||
if (LampOn == 1)
|
|
||||||
LampLit.Enable
|
|
||||||
endif
|
|
||||||
LampUnlit.Enable
|
|
||||||
endif
|
|
||||||
if (LadderPurchased == 1 && LadderDeployed == 1)
|
|
||||||
Ladder.Enable
|
|
||||||
endif
|
|
||||||
if (RockingEnabled == 0)
|
if (RockingEnabled == 0)
|
||||||
set fQuestDelayTime to MediumUpdateRate
|
set fQuestDelayTime to MediumUpdateRate
|
||||||
endif
|
endif
|
||||||
set Resetting to 1
|
set Resetting to 1
|
||||||
endif
|
endif
|
||||||
|
|
||||||
if (LampOn == 1 && LampLit.GetDisabled == 1)
|
if (LampOn == 1 && LampLit.GetDisabled == 1 && Resetting != -1)
|
||||||
set Resetting to 1
|
set Resetting to 1
|
||||||
LampLit.Enable
|
LampLit.Enable
|
||||||
LampUnlit.PlaySound3D SPLFireballFail
|
LampUnlit.PlaySound3D SPLFireballFail
|
||||||
elseif (LampOn == 0 && LampLit.GetDisabled == 0)
|
elseif (LampOn == 0 && LampLit.GetDisabled == 0 && Resetting != -1)
|
||||||
set Resetting to 1
|
set Resetting to 1
|
||||||
LampLit.Disable
|
LampLit.Disable
|
||||||
LampUnlit.PlaySound3D ITMTorchHeldExt
|
LampUnlit.PlaySound3D ITMTorchHeldExt
|
||||||
@@ -663,10 +659,14 @@ begin GameMode
|
|||||||
if (BoatMoving == 2 && CollisionDetectTimer > 0)
|
if (BoatMoving == 2 && CollisionDetectTimer > 0)
|
||||||
set CollisionDetectTimer to CollisionDetectTimer - SecondsPassed
|
set CollisionDetectTimer to CollisionDetectTimer - SecondsPassed
|
||||||
elseif (BoatMoving == 2 && CollisionDetectTimer <= 0 && Collider.GetInSameCell Player && Collider.GetPos z > CollisionDetectZThreshold)
|
elseif (BoatMoving == 2 && CollisionDetectTimer <= 0 && Collider.GetInSameCell Player && Collider.GetPos z > CollisionDetectZThreshold)
|
||||||
if (RockingEnabled == 0)
|
set ColliderActualX to Collider.GetPos x
|
||||||
set fQuestDelayTime to MediumUpdateRate
|
set ColliderActualY to Collider.GetPos y
|
||||||
|
if (ColliderActualX > ColliderX - ColliderPosThreshold && ColliderActualX < ColliderX + ColliderPosThreshold && ColliderActualY > ColliderY - ColliderPosThreshold && ColliderActualY < ColliderY + ColliderPosThreshold)
|
||||||
|
if (RockingEnabled == 0)
|
||||||
|
set fQuestDelayTime to MediumUpdateRate
|
||||||
|
endif
|
||||||
|
SetStage RYB 21 ; Collision procedure
|
||||||
endif
|
endif
|
||||||
SetStage RYB 21 ; Collision procedure
|
|
||||||
elseif (BoatMoving == 2 && CollisionDetectTimer <= 0 && Collider.GetDead == 1)
|
elseif (BoatMoving == 2 && CollisionDetectTimer <= 0 && Collider.GetDead == 1)
|
||||||
; Moving the collider every frame seems to cause the collider to spontaneously die when near land. However, this
|
; Moving the collider every frame seems to cause the collider to spontaneously die when near land. However, this
|
||||||
; also seems to have many false positives where it dies in open water. Instead of every frame, the script uses
|
; also seems to have many false positives where it dies in open water. Instead of every frame, the script uses
|
||||||
@@ -1119,7 +1119,7 @@ begin GameMode
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
; Boat rocking animation
|
; Boat rocking animation
|
||||||
if (RockingEnabled == 1 && Dragging == 0 && Summoning == 0 && Grounded == 0 && OnLand == 0 && PlayerDistance < RockDistanceThreshold)
|
if (RockingEnabled == 1 && Dragging == 0 && Summoning == 0 && Grounded == 0 && OnLand == 0 && PlayerNearBoat == 1)
|
||||||
; Update random variation
|
; Update random variation
|
||||||
if (RockRandomTimer <= 0)
|
if (RockRandomTimer <= 0)
|
||||||
set RockRandomTimer to RockRandomInterval
|
set RockRandomTimer to RockRandomInterval
|
||||||
@@ -1206,12 +1206,12 @@ begin GameMode
|
|||||||
endif
|
endif
|
||||||
endif
|
endif
|
||||||
|
|
||||||
if (BoatMoving >= 1 || Dragging >= 1 || Resetting >= 1 || (PlayerDistance < RockDistanceThreshold && (RockZOffset != 0 || RockPitchOffset != 0 || RockRollOffset != 0)))
|
if (BoatMoving >= 1 || Dragging >= 1 || Resetting >= 1 || (PlayerNearBoat == 1 && (RockZOffset != 0 || RockPitchOffset != 0 || RockRollOffset != 0)))
|
||||||
set ang to BoatAngle
|
set ang to BoatAngle
|
||||||
SetStage RYB 12 ; Calculate sin, cos, & tan for boat angle
|
SetStage RYB 12 ; Calculate sin, cos, & tan for boat angle
|
||||||
endif
|
endif
|
||||||
|
|
||||||
if (BoatMoving >= 1 || Dragging >= 1 || Resetting >= 1 || (PlayerDistance < RockDistanceThreshold && (RockZOffset != 0 || RockPitchOffset != 0 || RockRollOffset != 0)))
|
if (BoatMoving >= 1 || Dragging >= 1 || Resetting >= 1 || (PlayerNearBoat == 1 && (RockZOffset != 0 || RockPitchOffset != 0 || RockRollOffset != 0)))
|
||||||
; Transform world pitch/roll to boat's local pitch/roll
|
; Transform world pitch/roll to boat's local pitch/roll
|
||||||
set BoatPitchAngle to (RockPitchOffset + DragPitchAngle) * cos + RockRollOffset * sin
|
set BoatPitchAngle to (RockPitchOffset + DragPitchAngle) * cos + RockRollOffset * sin
|
||||||
set BoatRollAngle to -(RockPitchOffset + DragPitchAngle) * sin + RockRollOffset * cos
|
set BoatRollAngle to -(RockPitchOffset + DragPitchAngle) * sin + RockRollOffset * cos
|
||||||
@@ -1258,7 +1258,7 @@ begin GameMode
|
|||||||
Set ColliderMoveTimer to ColliderMoveFreq
|
Set ColliderMoveTimer to ColliderMoveFreq
|
||||||
endif
|
endif
|
||||||
endif
|
endif
|
||||||
elseif (BoatMoving == 0 && Dragging == 0 && Summoning == 0 && (PlayerDistance < RockDistanceThreshold && (RockZOffset != 0 || RockPitchOffset != 0 || RockRollOffset != 0)))
|
elseif (BoatMoving == 0 && Dragging == 0 && Summoning == 0 && (PlayerNearBoat == 1 && (RockZOffset != 0 || RockPitchOffset != 0 || RockRollOffset != 0)))
|
||||||
; Apply rocking to stationary boat.
|
; Apply rocking to stationary boat.
|
||||||
set BoatZWithRock to BoatZ + RockZOffset
|
set BoatZWithRock to BoatZ + RockZOffset
|
||||||
|
|
||||||
@@ -1271,7 +1271,7 @@ begin GameMode
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
; Update attachments (seat, chest, lamp, ladder) positions and angles both while moving and stationary (if rocking)
|
; Update attachments (seat, chest, lamp, ladder) positions and angles both while moving and stationary (if rocking)
|
||||||
if (BoatMoving >= 1 || Dragging >= 1 || Resetting >= 1 || (BoatMoving == 0 && Dragging == 0 && (PlayerDistance < RockDistanceThreshold && (RockZOffset != 0 || RockPitchOffset != 0 || RockRollOffset != 0))))
|
if (BoatMoving >= 1 || Dragging >= 1 || Resetting >= 1 || (BoatMoving == 0 && Dragging == 0 && (PlayerNearBoat == 1 && (RockZOffset != 0 || RockPitchOffset != 0 || RockRollOffset != 0))))
|
||||||
SetStage RYB 30 ; Calculate seat position and angle
|
SetStage RYB 30 ; Calculate seat position and angle
|
||||||
SetStage RYB 40 ; Update seat position and angle
|
SetStage RYB 40 ; Update seat position and angle
|
||||||
|
|
||||||
|
|||||||
294
UE4SSScripts/FixRowboatDuplication.lua
Normal file
294
UE4SSScripts/FixRowboatDuplication.lua
Normal file
@@ -0,0 +1,294 @@
|
|||||||
|
-- RowYourBoat/scripts/main.lua
|
||||||
|
local modBaseFormIDs = {
|
||||||
|
["boat"] = 0x0015a8,
|
||||||
|
["lamp_off"] = 0x007dbc,
|
||||||
|
["lamp_on"] = 0x007dbe,
|
||||||
|
["seat"] = 0x003ee1,
|
||||||
|
["chest"] = 0x005451,
|
||||||
|
["ladder"] = 0x006923,
|
||||||
|
}
|
||||||
|
local objectClasses = {
|
||||||
|
["boat"] = "BP_MS08Rowboat_C",
|
||||||
|
["lamp_off"] = "BP_ShipLamp01_C",
|
||||||
|
["lamp_on"] = "BP_ShipLamp300_C",
|
||||||
|
["seat"] = "BP_LCStool01F_C",
|
||||||
|
["chest"] = "BP_PCChestClutterLower01_C",
|
||||||
|
["ladder"] = "BP_RopeLadder01_C",
|
||||||
|
}
|
||||||
|
local objectClassToTypes = {}
|
||||||
|
for objectType, className in pairs(objectClasses) do
|
||||||
|
objectClassToTypes[className] = objectType
|
||||||
|
end
|
||||||
|
local objectPaths = {
|
||||||
|
["boat"] = "/Game/Forms/worldobjects/activator/BP_MS08Rowboat.BP_MS08Rowboat_C",
|
||||||
|
["lamp_off"] = "/Game/Forms/worldobjects/activator/BP_ShipLamp01.BP_ShipLamp01_C",
|
||||||
|
["lamp_on"] = "/Game/Forms/worldobjects/light/BP_ShipLamp300.BP_ShipLamp300_C",
|
||||||
|
["seat"] = "/Game/Forms/worldobjects/furniture/BP_LCStool01F.BP_LCStool01F_C",
|
||||||
|
["chest"] = "/Game/Forms/worldobjects/container/BP_PCChestClutterLower01.BP_PCChestClutterLower01_C",
|
||||||
|
["ladder"] = "/Game/Forms/worldobjects/door/BP_RopeLadder01.BP_RopeLadder01_C",
|
||||||
|
}
|
||||||
|
local destroyedObjects = {}
|
||||||
|
local primaryObjects = {}
|
||||||
|
local initialized = false
|
||||||
|
|
||||||
|
local function log(message)
|
||||||
|
print("[RowYourBoat] " .. message .. "\n")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Extract base ID (last 6 hex digits) from a full FormID
|
||||||
|
local function GetBaseID(formId)
|
||||||
|
if not formId then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
-- Mask off the load order bytes (first 2 hex digits)
|
||||||
|
return formId & 0xFFFFFF
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Get FormID from a boat
|
||||||
|
local function GetObjectFormID(object)
|
||||||
|
if not object or not object:IsValid() then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local refComponent = object.TESRefComponent
|
||||||
|
if not refComponent or not refComponent:IsValid() then
|
||||||
|
log("Object does not have a valid TESRefComponent")
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
return tonumber(refComponent.FormIDInstance)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Check if a object matches a specific base ID
|
||||||
|
local function MatchesBaseID(object, baseId, objectType)
|
||||||
|
if not baseId then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local formId = GetObjectFormID(object)
|
||||||
|
if not formId then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local objectBaseId = GetBaseID(formId)
|
||||||
|
log(string.format(string.upper(objectType) .. " Object FormID: %x, BaseID: %x == %x", formId, objectBaseId, baseId))
|
||||||
|
return objectBaseId == baseId
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Check if a boat is our mod-added boat
|
||||||
|
local function IsModObject(object, objectType)
|
||||||
|
return MatchesBaseID(object, modBaseFormIDs[objectType], objectType)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Check if a actor object is valid and should be considered
|
||||||
|
local function IsActorValid(object)
|
||||||
|
if not object or not object:IsValid() then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Check if we've already destroyed this object
|
||||||
|
local objectId = tostring(object)
|
||||||
|
if destroyedObjects[objectId] then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Skip default objects
|
||||||
|
local fullName = object:GetFullName()
|
||||||
|
if string.find(fullName, "Default__") then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
if object.bHidden == true or object.bActorIsBeingDestroyed == true then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Find all valid objects of type
|
||||||
|
local function FindAllObjects(objectType)
|
||||||
|
local objects = {}
|
||||||
|
local class = objectClasses[objectType]
|
||||||
|
local allObjects = FindAllOf(tostring(class))
|
||||||
|
|
||||||
|
if allObjects then
|
||||||
|
for i, object in pairs(allObjects) do
|
||||||
|
if IsActorValid(object) then
|
||||||
|
table.insert(objects, object)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return objects
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Set or validate the primary object
|
||||||
|
local function SetPrimaryObject(objects, objectType)
|
||||||
|
local primaryObject = primaryObjects[objectType]
|
||||||
|
-- If we already have a primary object and it's still valid, keep it
|
||||||
|
if primaryObject and primaryObject:IsValid() and IsActorValid(primaryObject) then
|
||||||
|
log(string.upper(objectType) .. " Primary object already set and valid: " .. primaryObject:GetFullName())
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Find the first valid mod object to be our primary
|
||||||
|
for _, object in ipairs(objects) do
|
||||||
|
primaryObjects[objectType] = object
|
||||||
|
log(string.upper(objectType) .. " Set primary mod object: " .. object:GetFullName())
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function DestroyObject(object, objectType)
|
||||||
|
if not object or not object:IsValid() then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local objectId = tostring(object)
|
||||||
|
local objectName = object:GetFullName()
|
||||||
|
|
||||||
|
-- Mark as destroyed first
|
||||||
|
destroyedObjects[objectId] = true
|
||||||
|
|
||||||
|
-- Hide and disable
|
||||||
|
local success = pcall(function()
|
||||||
|
object:SetActorHiddenInGame(true)
|
||||||
|
object:SetActorEnableCollision(false)
|
||||||
|
|
||||||
|
if object.SetActorTickEnabled then
|
||||||
|
object:SetActorTickEnabled(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
object:K2_DestroyActor()
|
||||||
|
end)
|
||||||
|
|
||||||
|
if success then
|
||||||
|
log(string.upper(objectType) .. " Destroyed duplicate: " .. objectName)
|
||||||
|
else
|
||||||
|
log(string.upper(objectType) .. " Failed to destroy object: " .. objectName .. " - " .. tostring(object))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Clean up duplicate objects for type
|
||||||
|
local function CleanupDuplicatesOfType(objectType)
|
||||||
|
log(string.upper(objectType) .. " CleanupDuplicatesOfType")
|
||||||
|
local objects = FindAllObjects(objectType)
|
||||||
|
log(string.upper(objectType) .. " Found " .. #objects .. " objects of type")
|
||||||
|
|
||||||
|
-- Count and collect mod objects
|
||||||
|
local modObjects = {}
|
||||||
|
for _, object in ipairs(objects) do
|
||||||
|
if IsModObject(object, objectType) then
|
||||||
|
table.insert(modObjects, object)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
log(string.upper(objectType) .. " Found " .. #modObjects .. " mod objects")
|
||||||
|
|
||||||
|
-- Set or validate primary object
|
||||||
|
SetPrimaryObject(modObjects, objectType)
|
||||||
|
|
||||||
|
-- If we have no duplicates, nothing to do
|
||||||
|
if #modObjects <= 1 then
|
||||||
|
if #modObjects == 0 then
|
||||||
|
log(string.upper(objectType) .. " No mod objects found, nulling primary object")
|
||||||
|
primaryObjects[objectType] = nil -- Reset if no mod boats exist
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
log(string.upper(objectType) .. " Found " .. #modObjects .. " mod objects, deleting primary one")
|
||||||
|
|
||||||
|
-- Delete all mod objects except the primary one
|
||||||
|
local removedCount = 0
|
||||||
|
local primaryModObject = primaryObjects[objectType]
|
||||||
|
if primaryModObject and primaryModObject:IsValid() then
|
||||||
|
for _, object in ipairs(modObjects) do
|
||||||
|
log(string.upper(objectType) .. " Object name: " .. object:GetFullName() .. " primaryObject name: " .. (primaryModObject and primaryModObject:GetFullName()) or "nil")
|
||||||
|
if object:GetFullName() == primaryModObject:GetFullName() then
|
||||||
|
DestroyObject(object, objectType)
|
||||||
|
removedCount = removedCount + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if removedCount > 0 then
|
||||||
|
log(string.upper(objectType) .. " Cleaned up " .. removedCount .. " duplicate object(s)")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Clean up duplicate objects
|
||||||
|
local function CleanupDuplicates()
|
||||||
|
for objectType, _ in pairs(objectClasses) do
|
||||||
|
CleanupDuplicatesOfType(objectType)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function detectAndDeleteDuplicateObject(object, objectType)
|
||||||
|
local attempts = 0
|
||||||
|
local maxAttempts = 100 -- 100ms total timeout
|
||||||
|
|
||||||
|
if not object or not object:IsValid() then
|
||||||
|
log(string.upper(objectType) .. " Object is not valid, skipping")
|
||||||
|
return false -- Skip invalid objects
|
||||||
|
end
|
||||||
|
|
||||||
|
local fullName = object:GetFullName()
|
||||||
|
if string.find(fullName, "Default__") or string.find(fullName, "_Generated_") then
|
||||||
|
log(string.upper(objectType) .. " Skipping default or generated object: " .. fullName)
|
||||||
|
return false -- Skip default or generated objects which are guaranteed to not be what we are looking for
|
||||||
|
end
|
||||||
|
|
||||||
|
local function waitForFormId()
|
||||||
|
local formId = GetObjectFormID(object)
|
||||||
|
if formId and formId ~= 0 then
|
||||||
|
log(string.upper(objectType) .. " Found object FormID in attempts: " .. attempts)
|
||||||
|
if IsModObject(object, objectType) then
|
||||||
|
log(string.upper(objectType) .. " New mod object detected: " .. object:GetFullName())
|
||||||
|
local primaryObject = primaryObjects[objectType]
|
||||||
|
if primaryObject and primaryObject:IsValid() and IsActorValid(primaryObject) then
|
||||||
|
log(string.upper(objectType) .. " Delete old primary object: " .. primaryObject:GetFullName())
|
||||||
|
DestroyObject(primaryObject, objectType)
|
||||||
|
log(string.upper(objectType) .. " Setting new primary object: " .. object:GetFullName())
|
||||||
|
primaryObjects[objectType] = object
|
||||||
|
else
|
||||||
|
log(string.upper(objectType) .. " No current primary object, setting primary: " .. object:GetFullName())
|
||||||
|
primaryObjects[objectType] = object
|
||||||
|
end
|
||||||
|
end
|
||||||
|
elseif attempts < maxAttempts and object and object:IsValid() then
|
||||||
|
attempts = attempts + 1
|
||||||
|
ExecuteWithDelay(1, waitForFormId) -- Check again after 1ms
|
||||||
|
else
|
||||||
|
log(string.upper(objectType) .. " Failed to get FormID for new object after 100 attempts")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
waitForFormId()
|
||||||
|
end
|
||||||
|
|
||||||
|
NotifyOnNewObject("/Script/Engine.Actor", function(object)
|
||||||
|
local objectName = object:GetFullName()
|
||||||
|
local className = string.match(objectName, "^([^%s]+)")
|
||||||
|
local objectType = objectClassToTypes[className]
|
||||||
|
if objectType then
|
||||||
|
log(string.upper(objectType) .. "Detected spawned object: " .. objectName)
|
||||||
|
detectAndDeleteDuplicateObject(object, objectType)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- -- Manual cleanup hotkey
|
||||||
|
-- RegisterKeyBind(Key.K, {ModifierKey.CONTROL}, function()
|
||||||
|
-- log("Manual cleanup triggered")
|
||||||
|
-- CleanupDuplicates()
|
||||||
|
-- end)
|
||||||
|
|
||||||
|
-- -- Debug key to reset primary boat
|
||||||
|
-- RegisterKeyBind(Key.K, {ModifierKey.ALT}, function()
|
||||||
|
-- log("Resetting primary boat")
|
||||||
|
-- primaryObjects = {}
|
||||||
|
-- destroyedObjects = {}
|
||||||
|
-- CleanupDuplicates()
|
||||||
|
-- end)
|
||||||
|
|
||||||
|
log("Script loaded!")
|
||||||
|
-- log(" CTRL-K = Manual cleanup (with debug info)")
|
||||||
|
-- log(" ALT-K = Reset primary boat selection")
|
||||||
Reference in New Issue
Block a user