Back to Index Page generated: Dec 20, 2024, 7:22:09 AM

Expansion Rock Hermit Beacons

Content

Warnings

  1. Information URL mismatch between OXP Manifest and Expansion Manager string length at character position 0

Manifest

from Expansion Manager's OXP list from Expansion Manifest
Description This OXP adds limited range beacons to the majority of the Rock Hermits, making them visible in Advanced Space Compass and 'In-system distances' Station screen if in range. This OXP adds limited range beacons to the majority of the Rock Hermits, making them visible in Advanced Space Compass and 'In-system distances' Station screen if in range.
Identifier oolite.oxp.dybal.rockHermitBeacons oolite.oxp.dybal.rockHermitBeacons
Title Rock Hermit Beacons Rock Hermit Beacons
Category Mechanics Mechanics
Author Dybal Dybal
Version 1.3 1.3
Tags rockhermit, beacon rockhermit, beacon
Required Oolite Version
Maximum Oolite Version
Required Expansions
Optional Expansions
Conflict Expansions
Information URL n/a
Download URL https://wiki.alioth.net/img_auth.php/c/cd/RockHermitBeacons-1.3.oxz n/a
License CC-BY-NC-SA 4.0 CC-BY-NC-SA 4.0
File Size n/a
Upload date 1610873474

Documentation

Also read http://wiki.alioth.net/index.php/Rock%20Hermit%20Beacons

Readme.txt

Rock Hermit Beacons
-------------------
v1.3, by Dybal

Most Rock Hermits start as mining communities in an asteroid-rich region, often on a shoestring budget (it's a huge capital investment), with no beacon, or a beacon with very limited range but still useful to find the way home in the asteroid field.

As time goes by, their wealth hopefully goes up and they can afford wider range beacons, which they might need since they have to go further to mine, as the asteroids closer to them become depleted of useful minerals by their mining activities.

Finally, the time comes when there are few useful asteroids around them, and they have the choice of moving on to richer fields, leaving an abandoned rock hermit, or convert into a station-like business, offering servicing, equipment, market, accommodation, etc., for which a still wider range beacon would allow them to attract a larger clientele.

Rock Hermit Beacons OXPs tries to bring the Rock Hermits closer to that story. It:

- adds beacons with diverse ranges, from 50km up to the whole system, to the majority of the Rock Hermits (Pirate Coves included), allowing them to be found through Advanced Space Compass and seen in the 'In-system distances' Station Interface screen (F4);
- adds and maintains an asteroid field around the Rock Hermit, the number of asteroids based on its beacon range.

The beacons are added to the Rock Hermit themselves, no Navigation Buoys are involved.

It doesn't add beacons to all Rock Hermits if finds, since some of them would be true hermits, and desire to be left alone, some would want to keep a low profile and not attract attention (from the government, from the police, from criminal gangs... the reason would vary with the Rock Hermit role and system government's meddling), and some simply can't afford yet to buy and maintain a beacon.


Note for OXP developers
-----------------------


This OXP doesn't create Rock Hermits, it acts on the Rock Hermits it finds, and it waits a bit after startup for other OXPs to create them..

It does three things, and all of them can be controlled by properties in the Rock Hermit's ship.script, so the Rock Hermit's creator can control what this OXP does with its creation.

- The first thing this OXP does is create beacons for the Rock Hermits a few seconds after startup. This can be controlled with the property 'ship.script.$rhbCreateBeacon'; Rock Hermit Beacons OXP will not create beacons on RHs that have this property set to the value "no".

- The second thing this OXP does is create and mantain an asteroid field around the RH. The size of the asteroid field can be controlled through the property 'ship.script.$rhbAsteroidsQtt'.

- Finally, it manages beacon visibility based on their range and the player's ship position. The beacon's range can be defined (in game meters) through 'ship.script.$rhbBeaconRange', and whenever 'ship.script.$rhbCreateBeacon' is "no" Rock Hermit Beacons will not change the beacon's visibility to On even if the beacons's range is defined and the player's ship moves into range, allowing the RH's creator to keep the beacon Off through that property.

License
-------

This work is licensed under Creative Commons Attribution Non Commercial Share Alike 4.0 International (CC BY-NC-SA 4.0)
(https://creativecommons.org/licenses/by-nc-sa/4.0/).

The rhb_chime.ogg sound is based on JustinBW's buttonchime02up.wav available at https://freesound.org/people/JustinBW/sounds/80921/ licensed under Creative Commons Attribution 3.0 International (CC BY 3.0).

Acknownledgments
---------------

This OXP started as a re-write of Eric Walch's Rock Hermit Locator (which was based on Frame's ideas), since it doesn't include a license allowing modification, but it took a life of its own after some suggestions from phkb induced  me to take a step back and look at the bigger picture, so my thanks to Frame, Eric Walch and phkb for the inspiration!

Version history
---------------

Version 1.3
* doesn't add beacons to Astromines (they replace Rock Hermits in Communist systems if Commies OXP is installed, and already have an unlimited beacon).

Version 1.2
* removes timers and resets Rock Hermit list when player's ship enters a wormhole
* renames beep.ogg to rhb_chime.ogg

Version 1.1
* Fixes bug in the handling of timer to repopulate asteroid fields

Version 1.0
* Initial release.

Equipment

This expansion declares no equipment. This may be related to warnings.

Ships

This expansion declares no ships. This may be related to warnings.

Models

This expansion declares no models. This may be related to warnings.

Scripts

Path
Scripts/rockHermitBeacons.js
"use strict";
this.name        = "RockHermitBeacons";
this.author      = "Dybal";
this.copyright   = "2020 Dybal";
this.license     = "CC BY-NC-SA 4.0";
this.description = "Adds beacons to Rock Hermits";
this.version     = "1.3";

this.$debug = false;
this.$logging = true;
this.$beep = new SoundSource;
this.$beep.sound = "rhb_chime.ogg";
this.$beaconCreationTimer;
// this.$rangeProbabilitiesProfiles has a list of profiles, each profile is a list of probablities for each range in this.$beaconRanges
this.$rangeProbabilitiesProfiles = [
                    // array of profiles with the probabilities for each beacon range
                    // the actual range value is found at similar position from the profile array in this.$beaconRanges
                    // probability for range 5 (range value 100000, unlimited) is 1-sum(probablities other ranges)
                    [ 0.02, 0.05, 0.15, 0.25, 0.30 ], // profile 0: system free+dangerous, honest
                    [ 0.10, 0.30, 0.20, 0.20, 0.15 ], // profile 1: system controlled+safe, honest 
                    [ 0.05, 0.20, 0.40, 0.20, 0.10 ], // profile 2: system meddling+safe, honest 
                    [ 0.05, 0.10, 0.40, 0.35, 0.10 ], // profile 3: system chaotic, honest 
                    [ 0.03, 0.07, 0.30, 0.30, 0.30 ], // profile 4: system free+dangerous, pirate
                    [ 0.05, 0.10, 0.40, 0.40, 0.05 ], // profile 5: system controlled+safe, pirate 
                    [ 0.05, 0.10, 0.30, 0.50, 0.05 ], // profile 6: system meddling+safe, pirate 
                    [ 0.05, 0.10, 0.40, 0.35, 0.10 ], // profile 7: system chaotic, pirate 
];
this.$beaconRanges = [   0,  100,  500, 1000, 2000, 100000 ]; // in km
this.$asteroidQtt =  [  50,  100,   50,   20,   20,     20 ];
this.$accumProbProfiles = []; // derived from this.$rangeProbabilitiesProfiles, each profile has the accumulated probabilities up to that range inclusive
this.$rolesRH = [ // Rock Hermit roles and the range probability profile (index of this.$rangeProbabilitiesProfiles) based on system government
//                                  by Government: An Fe Mu Di Cm Cf De Cp
    { role: "rockhermit",           rangeProfile: [ 0, 0, 3, 2, 1, 3, 2, 1 ], salt: 1 },
    { role: "rockhermit-chaotic",   rangeProfile: [ 0, 0, 3, 2, 1, 3, 2, 1 ], salt: 2 },
    { role: "rockhermit-pirate",    rangeProfile: [ 4, 4, 7, 6, 5, 7, 6, 5 ], salt: 3 },
    { role: "pirate-cove",          rangeProfile: [ 4, 4, 7, 6, 5, 7, 6, 5 ], salt: 4 }
];
this.$RHs = []; // list of Rock Hermits with beacons
this.$rangeVerifyTimer;
this.$populationSize = 20;  

//------------------------------------------------------------------------------------------------------------
// WorldScript Event Handlers
//

//------------------------------------------------------------------------------------------------------------
this.startUpComplete = this.shipExitedWitchspace = function _init() {
    var _rangeProfiles = this.$rangeProbabilitiesProfiles;
    var _acRangeProfiles = this.$accumProbProfiles;
    var i, j, r;

    for (i=0; i<_rangeProfiles.length; i++) {
        r = [];
        r.push(_rangeProfiles[i][0])
        for (j=1; j<_rangeProfiles[i].length; j++) {
            r.push(r[j-1] + _rangeProfiles[i][j]);
        }
        _acRangeProfiles.push(r);
    }

    // allow some some time for other OXPs to create the Rock Hermits
    if (this.$beaconsCreationTimer)
        this.$beaconsCreationTimer.start();
    else
        this.$beaconsCreationTimer = new Timer(this, this.$addBeacons, 3);
}

//------------------------------------------------------------------------------------------------------------
this.shipExitedWitchspace = function _shipExitedWitchspace() {
    if (this.$beaconsCreationTimer)
        this.$beaconsCreationTimer.start();
    else
        this.$beaconsCreationTimer = new Timer(this, this.$addBeacons, 3);
    this.$startRangeVerifyTimer();
}

//------------------------------------------------------------------------------------------------------------
this.shipWillEnterWitchspace = function _shipWillEnterWitchspace() {
    this.$stopRangeVerifyTimer(); 
    this.$stopAsteroidRepopulatorTimer();
    this.$RHs.length = 0;
}

//------------------------------------------------------------------------------------------------------------
this.shipLaunchedFromStation = function _shipWilllLaunchFromStation() {
    this.$startRangeVerifyTimer(); 
}

//------------------------------------------------------------------------------------------------------------
this.shipWillDockWithStation = function _shipWilllDockWithStation() {
    this.$stopRangeVerifyTimer(); 
}

//------------------------------------------------------------------------------------------------------------
// Internal functions
//
this.$addBeacons = function _addBeacons() {
    var ws = this;
    var _shipsWithPrimaryRole = system.shipsWithPrimaryRole.bind(system);
    var _scrambledPseudoRandomNumber = system.scrambledPseudoRandomNumber.bind(system);
    var _rolesDescList = this.$rolesRH;
    var _RHsWithBeacons = this.$RHs;
    var gov = system.government;
    var logging = ws.$logging;
    var i = _rolesDescList.length;
    var roleDesc, rh_list, j, rh, index, asteroids;

    delete this.$beaconsCreationTimer;

    while (i--) {
        roleDesc = _rolesDescList[i];
        if (ws.$debug) log (ws.name, "Adding beacons to Rock Hermits with role "+roleDesc.role);
        rh_list = _shipsWithPrimaryRole(roleDesc.role);
        var j = rh_list.length;
        while (j--) {
            rh = rh_list[j];
            if (rh.roles.indexOf("astromine") >= 0) {
                rh.script.$rhbCreateBeacon = "no";
                rh.script.$rhbAsteroidsQtt = 100;
            }
            if (rh.isValid) {
                if (!rh.script || !rh.script.$rhbCreateBeacon || rh.script.$rhbCreateBeacon !== "no") {
                    //  RH doesn't have a (creator/owner)-managed beacon
                    if (!rh.script) rh.setScript("oolite-default-ship-script");
                    rh.beaconLabel = rh.displayName; // nice name for ASC to display
                    rh.script.$rhbBeaconRange = this.$giveRange(_scrambledPseudoRandomNumber(roleDesc.salt), roleDesc.rangeProfile[gov]);
                    index = this.$beaconRanges.indexOf(rh.script.$rhbBeaconRange);
                    if (index >= 0) {
                        asteroids = this.$addAsteroids(this.$asteroidQtt[index], rh.position);
                        rh.script.$rhbAsteroidsQtt = asteroids.length;
                        if (logging) log(this.name, "Created "+asteroids.length+" asteroids around "+rh.displayName);
                    }
                    if (logging) log(ws.name, rh.displayName+" (role "+roleDesc.role+") has a beacon with range of "+rh.script.$rhbBeaconRange+"km");
                } else {
                    // RH is an astromine, OR
                    // RH creator said we shouldn't create a beacon, but let's look if we should manage visibility or asteroid field
                    if (logging) log(ws.name, rh.displayName+" is a managed Rock Hermit, any beacon should be turned On/Off by its creator");
                    if (rh.script && rh.script.$rhbAsteroidsQtt && rh.script.$rhbAsteroidsQtt > 0) {
                        asteroids = this.$addAsteroids(rh.script.$rhbAsteroidsQtt, rh.position);
                        if (logging) log(this.name, "Created "+asteroids.length+" asteroids around "+rh.displayName);
                    }
                }
                if (rh.script && rh.script.$rhbBeaconRange && rh.script.$rhbBeaconRange > 0)
                    _RHsWithBeacons.push(rh);
            }
        }
    }
    this.$startAsteroidRepopulatorTimer();
}

//------------------------------------------------------------------------------------------------------------
this.$repopulateAsteroids = function _repopulateAsteroids() {
    var ws = this;
    var _shipsWithPrimaryRole = system.shipsWithPrimaryRole.bind(system);
    var _scrambledPseudoRandomNumber = system.scrambledPseudoRandomNumber.bind(system);
    var _rolesDescList = this.$rolesRH;
    var _RHsWithBeacons = this.$RHs;
    var gov = system.government;
    var population_size = this.$populationSize;
    var logging = ws.$logging;
    var i = _rolesDescList.length;
    var roleDesc, rh_list, j, rh, n, asteroids;

    if (ws.$debug) log(this.name, "Verifying asteroid population around Rock Hermits");
    while (i--) {
        roleDesc = _rolesDescList[i];
        rh_list = _shipsWithPrimaryRole(roleDesc.role);
        var j = rh_list.length;
        while (j--) {
            rh = rh_list[j];
            if (rh.isValid) {
                if (rh.script && rh.script.$rhbAsteroidsQtt && rh.script.$rhbAsteroidsQtt > 0) {
                    n = system.countShipsWithPrimaryRole("asteroid", rh, 12000*Math.ceil(rh.script.$rhbAsteroidsQtt/population_size));
                    if (ws.$debug) log(this.name, "Found "+n+" asteroids less than "+12*Math.ceil(rh.script.$rhbAsteroidsQtt/population_size)+" km from "+rh.displayName);
                    if (n < rh.script.$rhbAsteroidsQtt) {
                        if (logging) log(this.name, "Asteroid population around "+rh.displayName+" decreased, there should be "+rh.script.$rhbAsteroidsQtt+", creating "+(rh.script.$rhbAsteroidsQtt - n)+" new asteroids");
                        this.$addAsteroids(rh.script.$rhbAsteroidsQtt - n, rh.position);
                    }
                }
            }
        }
    }
}

//------------------------------------------------------------------------------------------------------------
this.$addAsteroids = function _addAsteroids(num, pos) {
    var radius, n, i, asteroid;
    var asteroids = [];
    var population_size = this.$populationSize;
    var steps = Math.floor(n/population_size);
    i = 0;
    while (num > 0) {
        i++; 
        n = (num > population_size) ? population_size : num;
        num -= population_size;
        while (n--) { 
            asteroid = system.addShips("asteroid", 1, pos.add(Vector3D.randomDirection(i*12000)), 1);
            if (!asteroid) 
                log(this.name, "Could not add asteroid near Rock Hermit");
            else
                asteroids.push(asteroid);
        }
    }
    return asteroids;
}

//------------------------------------------------------------------------------------------------------------
this.$giveRange = function _giveRange(rnd, rangeProfileIndex) {
    var ws = this;
    var _rangeProfile = this.$accumProbProfiles[rangeProfileIndex];
    var _ranges = this.$beaconRanges;
    var i = 0;

    if (ws.$debug) log(ws.name, "accumulated range probabilities:"+_rangeProfile);
    while (i < _rangeProfile.length && rnd > _rangeProfile[i]) {
        if (ws.$debug) log(ws.name, i+":"+_rangeProfile[i]+", random:"+rnd.toFixed(3));
        i++ ;
    }
    if (ws.$debug) log(this.name, i+":"+_rangeProfile[i]+", random:"+rnd.toFixed(3));
    return _ranges[i];
}

//------------------------------------------------------------------------------------------------------------
this.$verifyBeaconsRange = function _switchBeaconsOn() {
    var ws = this;
    var _ship = player.ship;
    var _RHsWithBeacons = this.$RHs;
    var pos = _ship.position;
    var debug = ws.$debug;
    var logging = ws.$logging;
    var i = _RHsWithBeacons.length;
    var rh, distance;

    while (i--) {
        rh = _RHsWithBeacons[i];
        if (rh.isValid && rh.script && rh.script.$rhbBeaconRange) {
            distance = pos.distanceTo(rh)
            if (debug) log(this.name, rh.displayName+" has beacon range "+rh.script.$rhbBeaconRange+"km, distance:"+(distance/1000).toFixed(3)+", isVisible:"+rh.isVisible+", is ON:"+(rh.beaconCode != null))
            if (pos.distanceTo(rh) <= 1000 * rh.script.$rhbBeaconRange) {
                // beacon in range 
                if (!rh.script.$rhbCreateBeacon || rh.script.$rhbCreateBeacon != "no") {
                    //  RH's creator alows beacon visibility management
                    if (rh.beaconCode == null) {
                        // beaocn is Off, turn it On
                        rh.beaconCode = (rh.script.$rhbBeaconCode ? rh.script.$rhbBeaconCode : "Rock Hermit");
                        if (logging) log(ws.name, rh.displayName+" beacon in range");
                        if (_ship.equipmentStatus("EQ_ADVANCED_COMPASS") === "EQUIPMENT_OK") {
                            this.$beep.play();
                            player.commsMessage(rh.displayName+" beacon detected", 5)
                        }
                    }
                }
            } else if (rh.beaconCode) {
                // RH out of beacon range, turn beacon OFF
                rh.script.$rhbBeaconCode = rh.beaconCode;
                rh.beaconCode = null;
                if (logging) log(ws.name, rh.displayName+" beacon out of range");
            }
        }
    }
}

//------------------------------------------------------------------------------------------------------------
this.$startRangeVerifyTimer = function _startRangeVerifyTimer() {
    if (this.$rangeVerifyTimer)
        this.$rangeVerifyTimer.start()
    else
        this.$rangeVerifyTimer = new Timer(this, this.$verifyBeaconsRange, 1, 2);
}

//------------------------------------------------------------------------------------------------------------
this.$stopRangeVerifyTimer = function _stopRangeVerifyTimer() {
    if (this.$rangeVerifyTimer) {
        if (this.$rangeVerifyTimer.isRunning)
            this.$rangeVerifyTimer.stop();
        delete this.$rangeVerifyTimer;
    }
}

//------------------------------------------------------------------------------------------------------------
this.$startAsteroidRepopulatorTimer = function _startAsteroidRepopulatorTimer() {
    if (this.$asteroidRepopulatorTimer)
        this.$asteroidRepopulatorTimer.start()
    else
        this.$asteroidRepopulatorTimer = new Timer(this, this.$repopulateAsteroids, 1, 300); // every 5 minutes
}

//------------------------------------------------------------------------------------------------------------
this.$stopAsteroidRepopulatorTimer = function _stopAsteroidRepopulatorTimer() {
    if (this.$asteroidRepopulatorTimer) {
        if (this.$asteroidRepopulatorTimer.isRunning)
            this.$asteroidRepopulatorTimer.stop()
        delete this.$asteroidRepopulatorTimer;
    }
}