Old Post IDEK's Logistics Station integration

Discussion in 'Salvage Beacons (Mod Questions Here) (Archive)' started by EyeDeck, Apr 16, 2018.

  1. EyeDeck

    EyeDeck Active Member

    Joined:
    Aug 26, 2017
    Messages:
    114
    Likes Received:
    125

    After getting a bunch of requests for this, I finally looked into it today. I've written a feature so that level 2+ Logistics Stations (the level at which the plot gets the ham radio and radio tower) will register themselves as "virtual" Communications Desks.
    Code:
    ; --------------- Salvage Beacons Compat ---------------
    ; Plugin = SalvageBeacons.esp
    ; SB_CommunicationsDesk                    0x3D5F
    ; SB_SalvageBeaconsQuest =                0x80F9
    ;    ActiveCommStations =                    alias index 81
    ; PlayerHasWorkedCommStation =             0x2673
    ; DLCFarHarborHasWorkedCommStation =    0x268D
    ;    DLC03FarHarbor "The Island" =            0xB0F in DLCCoast.esm
    ; SB_HasActiveCommStation                 0x2675
    string SBstr = "SalvageBeacons.esp" const
    
    Function AddVirtualCommDesk(WorkshopScript thisWorkshop)
        lsTrace("SB Compat: Registering virtual Comm Desk to " + thisWorkshop + "...")
       
        Keyword SB_HasActiveCommStation = Game.GetFormFromFile(0x2675, SBstr) as Keyword
        GlobalVariable PlayerHasWorkedCommStation = Game.GetFormFromFile(0x2673, SBstr) as GlobalVariable
        GlobalVariable DLCFarHarborHasWorkedCommStation = Game.GetFormFromFile(0x268D, SBstr) as GlobalVariable
        Quest SB_SalvageBeaconsQuest = Game.GetFormFromFile(0x80F9, SBstr) as Quest
        RefCollectionAlias ActiveCommStations = SB_SalvageBeaconsQuest.GetAlias(81) as RefCollectionAlias
        Worldspace DLC03FarHarbor = Game.GetFormFromFile(0xB0F, "DLCCoast.esm") as Worldspace
       
        ActiveCommStations.AddRef(thisWorkshop)
        thisWorkshop.AddKeyword(SB_HasActiveCommStation)
       
        if (thisWorkshop.GetWorldspace() != DLC03FarHarbor)
            PlayerHasWorkedCommStation.SetValue(1)
        else
            DLCFarHarborHasWorkedCommStation.SetValue(1)
        endif
       
        lsTrace("...finished registering virtual comm desk.")
    EndFunction
    
    Function RemoveVirtualCommDesk(WorkshopScript thisWorkshop)
        lsTrace("SB Compat: Removing virtual Comm Desk from " + thisWorkshop + "...")
        ; first check if any actual comm desks are here - if so just let them handle this and bail out
        ObjectReference[] thisSettlementCommStations = thisWorkshop.FindAllReferencesOfType(Game.GetFormFromFile(0x3D5F, SBstr), 10000)
        if (thisSettlementCommStations.length)
            ScriptObject commStation = thisSettlementCommStations[0].CastAs("SB_commdesk")
            Var[] args = new var[1]
            args[0] = thisWorkshop
            commStation.CallFunctionNoWait("FindActiveCommStation", args)
            lsTrace("...Responsibility passed onto " + commStation + ".")
            return
        endif
        ; it's in SB_commdesk.FindActiveStation(), so it seems appropriate here
        Utility.Wait(5)
       
        Keyword SB_HasActiveCommStation = Game.GetFormFromFile(0x2675, SBstr) as Keyword
        GlobalVariable PlayerHasWorkedCommStation = Game.GetFormFromFile(0x2673, SBstr) as GlobalVariable
        GlobalVariable DLCFarHarborHasWorkedCommStation = Game.GetFormFromFile(0x268D, SBstr) as GlobalVariable
        Quest SB_SalvageBeaconsQuest = Game.GetFormFromFile(0x80F9, SBstr) as Quest
        RefCollectionAlias ActiveCommStations = SB_SalvageBeaconsQuest.GetAlias(81) as RefCollectionAlias
        Worldspace DLC03FarHarbor = Game.GetFormFromFile(0xB0F, "DLCCoast.esm") as Worldspace
       
        ActiveCommStations.RemoveRef(thisWorkshop)
        thisWorkshop.RemoveKeyword(SB_HasActiveCommStation)
       
        bool isFarHarbor = (thisWorkshop.GetWorldspace() == DLC03FarHarbor)
       
        bool otherActiveCommStation = false
        bool otherActiveFarHarborCommStation = false
        if (!isFarHarbor)
            for int i = 0 to ActiveCommStations.GetCount() - 1
                if (ActiveCommStations.GetAt(i).GetWorldspace() != DLC03FarHarbor)
                    otherActiveCommStation = true
                    break
                endif
            endfor
        else
            for int i = 0 to ActiveCommStations.GetCount() - 1
                if (ActiveCommStations.GetAt(i).GetWorldspace() == DLC03FarHarbor)
                    otherActiveFarHarborCommStation = true
                    break
                endif
            endfor
        endif
       
        if (!isFarHarbor && !otherActiveCommStation)
            lsTrace("\tCould not find another comm station - setting PlayerHasWorkedCommStation to 0.")
            PlayerHasWorkedCommStation.SetValue(0)
        endif
        if (isFarHarbor && !otherActiveFarHarborCommStation)
            lsTrace("\tCould not find another comm station in Far Harbor - setting DLCFarHarborHasWorkedCommStation to 0.")
            DLCFarHarborHasWorkedCommStation.SetValue(0)
        endif
       
        lsTrace("...finished unregistering virtual comm desk.")
    EndFunction
    ; --------------- End Salvage Beacons Compat ---------------
    This was one of those rare cases where the code I wrote compiled and worked as expected the first time, despite being a heavily reverse-engineered hack.

    Anyway, since this functionally eliminates the need to build any of Salvage Beacons' native communications desks at all provided you're using my mod, @kinggath, is it okay if I release this? I intend for this feature to be disabled by default, even if Salvage Beacons is detected, so the user will have to knowingly go into the settings and flip the switch to turn it on.
     
    El Rizzo, VersusXV, Loredena and 2 others like this.
  2. Yagisan

    Yagisan Rebuilding the Commonwealth. Staff Member Vault Librarian Verified Builder

    Joined:
    Jul 8, 2017
    Messages:
    518
    Likes Received:
    465

    If this does, what I think it does - this just made all my city plans extra awesome.
     
  3. pra

    pra Well-Known Member Staff Member Verified Builder

    Joined:
    Jul 6, 2017
    Messages:
    407
    Likes Received:
    386

    Hm, what happens if you have a logistics station in a mod-added settlement? I haven't reverse-engineered salvage beacons, but I have seen references to vanilla settlements in there, and assumed it's all hardcoded
     
  4. EyeDeck

    EyeDeck Active Member

    Joined:
    Aug 26, 2017
    Messages:
    114
    Likes Received:
    125

    Most (all?) of the hard-coded stuff looks like it's either to correct the calculation for where the closest comm station is that determines where to dispatch a salvage team from, and how long it should take, or to override where the salvage team returns to.

    I can't say it's how I'd have written it; I had to solve a similar problem for my Logistics Stations mod, and I ended up calculating distance based on the location of a settlement's map marker (which is always placed in a worldspace, so x/y coords are accurate), and also applying a coordinate offset from a small database (literally just one entry for Far Harbor and another one for Nuka World, though expandable up to the Papyrus array limit) in case the marker wasn't in the Commonwealth worldspace. I'm picky about my code, though, so the thought of having to hard-code anything at the per-settlement level was unacceptable to me.
     
  5. pra

    pra Well-Known Member Staff Member Verified Builder

    Joined:
    Jul 6, 2017
    Messages:
    407
    Likes Received:
    386

    I agree, hardcoding stuff sucks. I guess what my question boils down to is: do salvage beacons work in mod-added settlements, for which no hardcoded data exists? I never tried that, fearing that it would break my save.
     
  6. kinggath

    kinggath Well-Known Member Staff Member Administrator Moderator Verified Builder

    Joined:
    May 8, 2017
    Messages:
    3,270
    Likes Received:
    2,586

    Yeah you can release it.

    Almost nothing is hard-coded in Salvage Beacons except for references to worldspaces and their entry points which are used to do calculate travel time to NukaWorld’s entrance and to keep Far Harbor isolated as a separate network. It also has a hard-cap on travel time, because occasionally GetDistance returns ridiculous numbers that could result in a month of travel time.

    I will be rewriting Salvage Beacons from scratch at some point, but I imagine the keyword on workshops will always be the means of detecting desks as it’s required by the settlement select menu function.
     
    Dritz, Loredena and pra like this.
  7. EyeDeck

    EyeDeck Active Member

    Joined:
    Aug 26, 2017
    Messages:
    114
    Likes Received:
    125

    Neat. I don't like interfering with other modders' mods without asking them is all, since there are plenty out there that would be absolutely livid if I did something like this without asking.

    When you get around to it, doing the sort of thing I did for ILS might not be a bad idea:
    Code:
    ; ------------ From the main data storage quest ------------
    Struct WorldspaceOffset
        Worldspace thisWs
        float x
        float y
        {Offsets to apply to the origin of this worldspace, which get added to settlements in this worldspace's x/y coords}
        float portalAX
        float portalAY
        {Location of map marker relative to this worldspace's coords}
        Worldspace connectedWS
        float portalBX
        float portalBY
        {Location of map marker in the connected worldspace's coords}
    EndStruct
    
    bool getMarkerSpinLock
    ObjectReference Function GetMarkerFromLocation(Location loc)
        while (getMarkerSpinLock)
            ; this hack uses the story manager to get a reference for us, and as such it obviously can only get one thing at a time,
            ; so make sure the quest is shut down before we try to start it more than once
            lsTrace("A thread is waiting in the GetMarkerFromLocation spin lock.")
            Utility.WaitMenuMode(0.5)
        endwhile
        getMarkerSpinLock = true
        ObjectReference toReturn = None
        if (ILS_FindMapMarkerKeyword.SendStoryEventAndWait(loc))
            toReturn = MapMarkerHackRef.GetReference()
            ILS_MapMarkerHackQuest.Stop()
            if (toReturn != None)
                lsTrace("Successfully resolved location " + loc + "'s map marker ref: " + toReturn)
            else
                lsTrace("Warning: Could not resolve a map marker for location " + loc + ", perhaps modded workshop set up incorrectly?")
            endif
        else
            lsTrace("Warning: Tried to GetMarkerFromLocation, but no quest started!")
        endif
        getMarkerSpinLock = false
        return toReturn
    EndFunction
    
    ; ------------ From a different update/compatibility script (the one which does stuff on game load) ------------
    string sDLC03 = "DLCCoast.esm" const
    string sDLC04 = "DLCNukaWorld.esm" const
    Function CheckCompat()
        LogisticsParentScript:WorldspaceOffset[] newOffsets = new LogisticsParentScript:WorldspaceOffset[0]
      
        if (Game.IsPluginInstalled(sDLC03))
            AddWSOffsetData(newOffsets, 100000.0, 300000.0, 0x3FEE3, 0x3FEE5, sDLC03) ; DLC03ToCommonwealthMapMarkerREF [REFR:0103FEE3], DLC03ToFarHarborMapMarker [REFR:0103FEE5]
        endif
      
        if (Game.IsPluginInstalled(sDLC04))
            AddWSOffsetData(newOffsets, -300000.0, 20000.0, 0x25517, 0x25515, sDLC04) ; DLC04CommonwealthMapMarker [REFR:02025517], DLC04NukaWorldMapMarker [REFR:02025515]
        endif
      
        LogisticsParent.WorldspaceOffsets = newOffsets
    EndFunction
    
    Function AddWSOffsetData(LogisticsParentScript:WorldspaceOffset[] woArr, float x, float y, int portalIDA, int portalIDB, string wsPlugin)
        ObjectReference portalA = Game.GetFormFromFile(portalIDA, wsPlugin) as ObjectReference
        ObjectReference portalB = Game.GetFormFromFile(portalIDB, wsPlugin) as ObjectReference
        LogisticsParentScript:WorldspaceOffset wo = new LogisticsParentScript:WorldspaceOffset
        wo.thisWS = portalA.GetWorldspace()
        wo.x = x
        wo.y = y
        wo.portalAX = portalA.X + x
        wo.portalAY = portalA.Y + y
        wo.connectedWS = portalB.GetWorldspace()
        wo.portalBX = portalB.X
        wo.portalBY = portalB.Y
        woArr.Add(wo)
    EndFunction
    The basic structure is that a cache of stations with references + location data is kept in a struct array, and when I cache the x/y coordinates of each station, I get the map marker from the relevant location (either by grabbing out of the workbench's script property, or using the function above in case that fails), then get its worldspace, and if it isn't in the Commonwealth, I offset the coordinates with the data in the WorldspaceOffsets array. Then, when I need to do a distance check, I do it directly in Papyrus via Math.sqrt(Math.pow((x1-x2),2) + Math.pow((y1-y2),2) + Math.pow((z1-z2),2)), using the worldspace-corrected coordinate values.

    That would also allow you to drop the Far Harbor-specific compatibility stuff, since you could just crank up the distance, e.g. set the offset for that worldspace to (1m, 3m), and let a raw distance check determine whether there are any comm desks in range.

    For that mod, though, it's important that all of the code that does that is very performant; it needs to be able to do up to 128^2 distance checks in a very short period of time in order to calculate the minimum spanning tree properly, which obviously means that any sort of delayed function calls (GetDistance etc) would make the calculation take ridiculous amounts of time.

    If I have to rewrite or update it later, it's no big deal. All of the research + code writing combined only took me an hour or so in the first place.
     
    Last edited: Apr 16, 2018
  8. Caelaran

    Caelaran New Member

    Joined:
    Mar 18, 2018
    Messages:
    3
    Likes Received:
    0

    When are you thinking of deploying this to Nexus? Once it's out there I'll give it a whirl :)
     
  9. EyeDeck

    EyeDeck Active Member

    Joined:
    Aug 26, 2017
    Messages:
    114
    Likes Received:
    125

    @Caelaran, Well, I think my local version is in a decent enough state at the moment, so maybe tonight after a bit of extra testing to be sure I haven't done anything stupid.

    Edit: For what it's worth I did actually find some code that wasn't working properly because of a compiler bug, of all things. This here is why testing is important.
     
    Last edited: Apr 17, 2018
  10. Jonnan

    Jonnan Active Member

    Joined:
    Dec 3, 2017
    Messages:
    298
    Likes Received:
    164

    Uh - suggestion? If these are being combined in this way (Which is great) could the combo be updated so instead of delivering to a specific settlement workshop, the delivery went to the nearest logistics locker?
     
  11. EyeDeck

    EyeDeck Active Member

    Joined:
    Aug 26, 2017
    Messages:
    114
    Likes Received:
    125

    @Jonnan It wouldn't be possible to do that without code on Salvage Beacons' side, anyway it would be functionally identical to manually sending the contents to the workbench with the storage designator in it--logistics lockers aren't containers themselves, rather they're just activators that let you open the inventory remotely.
     
  12. Jonnan

    Jonnan Active Member

    Joined:
    Dec 3, 2017
    Messages:
    298
    Likes Received:
    164

    Fair 'nuff - I just thought it would shorten the period to deliver the cache if you could shortcut to the nearest settlement rather than selecting the 'Warehouse' settlement which can be farther.

    Admittedly, the logistics station 'collection' options actually rather cover that anyway - everything *is* eventually delivered to the logistics workbench if you have it setup, so this is a bit redundant anyway.
     
  13. Cap'N Squirrel

    Cap'N Squirrel New Member

    Joined:
    Dec 23, 2018
    Messages:
    2
    Likes Received:
    0

    Hello Kinggath and EyeDeck,
    For clarity: I have both installed through Bethesda's Mod Install and not Nexus Mods.

    I have a few short answered questions I am hoping you two can answer quickly about both IDEKs Logistics and Salvage Bacons work. I think I have most of these questions answered already, but I'm just looking for clarity.

    Salvage Beacon Questions:
    1) Can you deploy more than 1 Beacon out in the wasteland at the same time, or will it delete the prior beacons?
    2) Does the beacon also pick up everything in the stash or just junk. (I'm hoping it also takes Armor, Weapons, and Food/Drink to help with settlement donations).

    IDEK Logistics with Salvage Beacons turned on.
    1) I have 2 Logistic LVL 1 stations (Red Rocket and Sanctuary (Both LVL 0 Settlements) but I get message that I can't deploy a beacon till I have a Comms station. Is this because they are LVL 1 and I have to wait for them to Upgrade to LVL 2?
    2) Will the salvage be deposited in the stations storage?
    3) Is the Storage automatically shared with the Workbenches, and if Yes is the storage global or Local to the Settlement?
     
  14. EyeDeck

    EyeDeck Active Member

    Joined:
    Aug 26, 2017
    Messages:
    114
    Likes Received:
    125

    • The ILS/Salvage Beacons integration only kicks in at level 2 when the station gets a radio tower. Or that was the idea anyway, it works with the level 2/3 interior plot too despite it never getting a radio tower (for obvious reasons).
    • Nope, it goes in the workbench. All ILS does is "trick" Salvage Beacons into thinking it has a comm desk in the settlement by manipulating some of SB's internal data stores, so it is functionally identical to a normal comm desk.
    • Ideally you should put ILS's storage thing in a workbench; I tried seeing if there was a way to make an arbitrary container behave as if it were connected to a supply line network, but ultimately concluded that it isn't possible. It might be possible if I were to make certain tweaks to one of the workshop scripts (I don't remember which specific one atm), but that's a mess I don't want to get into.
     
  15. Cap'N Squirrel

    Cap'N Squirrel New Member

    Joined:
    Dec 23, 2018
    Messages:
    2
    Likes Received:
    0

    Thank you so much for getting back to me so fast!!
    That's great to know that is kicks in at LVL 2.
    It's great to have Salvage Beacons tied to the Settlements.
     
  16. Jonnan

    Jonnan Active Member

    Joined:
    Dec 3, 2017
    Messages:
    298
    Likes Received:
    164

    As a quick aside, on my last game I kept waiting for a L2 salvage station and at some point realized that the Salvage Beacon integration was toggled off in the settings. Not sure if that's the default and I just hadn't had a new game for awhile but if it's not working you might double check that.

    Also, if you have Automatron the HQ you get there is never attacked and is a great place to set your logistics center store. The Castle is only attacked during scripted mass attacks and is a good second option.
     

Share This Page