Star Sector How to Continue Campaign

Icon check temp.png

Only up to date for version 0.9.1. It is likely still broadly correct but not verified for the most up to date data yet. Please double check the Version History

Description [ ]

A quest is a mission that is offered during the campaign (not to be confused with Missions). It consists of one or more stages. For example, a quest to deliver goods to a specific market is a single stage. The quest in which the player acquires the Planetary Shield building, by comparison, requires the player travel to multiple points during multiple stages.

Creating a quest requires some knowledge of Java or Kotlin (what is this?) and cannot be done only by modifying json and csv files.

Below here, this page describes the pre-0.95.0 way of creating quests, which still works but there is a new possible approach. The Internal Affairs mod is an excellent example of the 0.95.0 approach to building quests.


The bulk of a typical quest is contained in two parts; the BarEvent and the BaseIntelPlugin. Additionally, an InteractionDialog may be used to display dialogs outside of the bar.

Note: "quest" is a loose definition. A quest could be offered randomly when the player enters a system, with no BarEvent and direct them to a specific location without using Intel either; just InteractionDialogs. However, this page is going to cover the most common and expected type, which is offered at a bar and tracked using Intel.

We're going to build a quest where the player is told to travel to a random planet - a simple "go here" quest. It will only be completable once.

Structure [ ]

The three components above (BarEvent, BaseIntelPlugin, and possibly InteractionDialog) are technically all that is needed to build a quest (plus a bit more code to get the game to recognize them). However, it makes it much easier to read and manage if we have a "coordinator" class as well.

  • BarEvent defines what the player sees when they enter the bar, as well as the dialog where they can accept or reject the quest offer.
  • BarEventCreator is used by the game to create the BarEvent when needed.
  • BaseModPlugin is needed to actually add your BarEvent to the game.
  • A coordinator class tracks the player's progress, holds constant variables, and moves the player from one stage to another. It's best to make this a static class that holds no state; this will be explained later.
  • BaseIntelPlugin displays the player's progress in the Intel screen and shows them what to do next.
  • InteractionDialogPlugin can be used to show a dialog outside of the bar. For example, we may want to display "The cargo is unloaded and the manager hands you your payment of 100,000 credits" when the player lands on a planet. We're not limited to displaying dialogs on planets, however.
  • And finally, a BaseCampaignPlugin can be useful to show InteractionDialogs.

BarEvent [ ]

The place where it all starts; at the bar. Quests don't need to start at the bar, but that is the common place and is where the example quest will start.

To create our bar event, we'll need to choose and implement the interface PortsideBarEvent. There are a few ready-made implementations to chose from.

BaseBarEvent - Bare-bones, abstract implementation of PortsideBarEvent. Very flexible.

BaseBarEventWithPerson - Abstract implementation of BaseBarEvent that adds a person for the user to talk to with a customizable gender, job, faction, portrait, etc.

BaseGetCommodityBarEvent - Abstract implementation for a quest where the user needs to bring some commodity somewhere. "A concerned-looking man is sitting at a corner table..." is an example of such a quest.

We're going to get our quest from a person at the bar, so we're going to take BaseBarEventWithPerson and create a subclass of it called DemoBarEvent. It can be placed anywhere.

Now, there are a few methods that we'll want to override.

                                                public            class            DemoBarEvent            extends            BaseBarEventWithPerson            {                        /**                                      * True if this event may be selected to be offered to the player,                                      * or false otherwise.                                      */                        public            boolean            shouldShowAtMarket            (            MarketAPI            market            )            {                        // add any conditions you want                        return            super            .            shouldShowAtMarket            (            market            )            &&            !            getMarket            ().            getFactionId            ().            equals            (            "luddic_path"            );                        }                                    /**                                      * Set up the text that appears when the player goes to the bar                                      * and the option for them to start the conversation.                                      */                        @Override                        public            void            addPromptAndOption            (            InteractionDialogAPI            dialog            ,            Map            <            String            ,            MemoryAPI            >            memoryMap            )            {                        // Calling super does nothing in this case, but is good practice because a subclass should                        // implement all functionality of the superclass (and usually more)                        super            .            addPromptAndOption            (            dialog            ,            memoryMap            );                        regen            (            dialog            .            getInteractionTarget            ().            getMarket            ());            // Sets field variables and creates a random person                                    // Display the text that will appear when the player first enters the bar and looks around                        dialog            .            getTextPanel            ().            addPara            (            "A small crowd has gathered around a "            +            getManOrWoman            ()            +            " who looks to be giving "            +                        "some sort of demonstration."            );                                    // Display the option that lets the player choose to investigate our bar event                        dialog            .            getOptionPanel            ().            addOption            (            "See what the demonstration is about"            ,            this            );                        }                                    /**                                      * Called when the player chooses this event from the list of options shown when they enter the bar.                                      */                        @Override                        public            void            init            (            InteractionDialogAPI            dialog            ,            Map            <            String            ,            MemoryAPI            >            memoryMap            )            {                        super            .            init            (            dialog            ,            memoryMap            );                        // Choose where the player has to travel to                        DemoQuestCoordinator            .            initQuest            ();                                    // If player starts our event, then backs out of it, `done` will be set to true.                        // If they then start the event again without leaving the bar, we should reset `done` to false.                        done            =            false            ;                                    // The boolean is for whether to show only minimal person information. True == minimal                        dialog            .            getVisualPanel            ().            showPersonInfo            (            person            ,            true            );                                    // Launch into our event by triggering the "INIT" option, which will call `optionSelected()`                        this            .            optionSelected            (            null            ,            OptionId            .            INIT            );                        }                                    /**                                      * This method is called when the player has selected some option for our bar event.                                      *                                      * @param optionText the actual text that was displayed on the selected option                                      * @param optionData the value used to uniquely identify the option                                      */                        @Override                        public            void            optionSelected            (            String            optionText            ,            Object            optionData            )            {                        if            (            optionData            instanceof            OptionId            )            {                        // Clear shown options before we show new ones                        dialog            .            getOptionPanel            ().            clearOptions            ();                                    // Handle all possible options the player can choose                        switch            ((            OptionId            )            optionData            )            {                        case            INIT            :                        // The player has chosen to walk over to the crowd, so let's tell them what happens.                        dialog            .            getTextPanel            ().            addPara            (            "You walk over and see that the "            +            getManOrWoman            ()            +                        " is showing the crowd how to create quest mods for a video game."            );                        dialog            .            getTextPanel            ().            addPara            (            "It seems that you can learn more by traveling to "            +                        DemoQuestCoordinator            .            getDestinationPlanet            ().            getName            ());                                    // And give them some options on what to do next                        dialog            .            getOptionPanel            ().            addOption            (            "Take notes and decide to travel to learn more"            ,            OptionId            .            TAKE_NOTES            );                        dialog            .            getOptionPanel            ().            addOption            (            "Leave"            ,            OptionId            .            LEAVE            );                        break            ;                        case            TAKE_NOTES            :                        // Tell our coordinator class that the player just started the quest                        DemoQuestCoordinator            .            startQuest            ();                                    dialog            .            getTextPanel            ().            addPara            (            "You take some notes. Quest mods sure seem like a lot of work..."            );                        dialog            .            getOptionPanel            ().            addOption            (            "Leave"            ,            OptionId            .            LEAVE            );                        break            ;                        case            LEAVE            :                        // They've chosen to leave, so end our interaction. This will send them back to the bar.                        // If noContinue is false, then there will be an additional "Continue" option shown                        // before they are returned to the bar. We don't need that.                        noContinue            =            true            ;                        done            =            true            ;                                    // Removes this event from the bar so it isn't offered again                        BarEventManager            .            getInstance            ().            notifyWasInteractedWith            (            this            );                        break            ;                        }                        }                        }                                    enum            OptionId            {                        INIT            ,                        TAKE_NOTES            ,                        LEAVE                        }                        }          


Now that we have created our quest offer at the bar, we need to actually tell Starsector to offer it at the bar.

BarEventCreator [ ]

The BarEventCreator is a small class Starsector uses to, unsurprisingly, create bar events. The game keeps a list of these creators and periodically creates bar events for the player to find.

There is an abstract default implementation of BarEventCreator that we will use; BaseBarEventCreator. For missions that should be offered frequently and constantly, DeliveryBarEventCreator is also an option.

This class can be implemented with minimal code.

                                    public            class            DemoBarEventCreator            extends            BaseBarEventCreator            {                        @Override                        public            PortsideBarEvent            createBarEvent            ()            {                        return            new            DemoBarEvent            ();                        }                        }          

There are additional methods (viewable here) that can be overriden to customize how frequently and/or rarely the player should encounter the event.

BaseModPlugin [ ]

A BaseModPlugin is what we use to tell Starsector what to load and when. It has hooks (methods) that are called by the game itself.

If this guide has been followed sequentially, we now have a BarEvent and a BarEventCreator, but the game doesn't yet know how to add these to a bar. We can use the onGameLoad method to do so.

                                    public            class            DemoBaseModPlugin            extends            BaseModPlugin            {                                    /**                                      * Called when the player loads a saved game.                                      *                                      * @param newGame true if the save game was just created for the first time.                                      *                Note that there are a few `onGameLoad` methods that may be a better choice than using this parameter                                      */                        @Override                        public            void            onGameLoad            (            boolean            newGame            )            {                        super            .            onGameLoad            (            newGame            );                                    BarEventManager            barEventManager            =            BarEventManager            .            getInstance            ();                                    // If the prerequisites for the quest have been met (optional) and the game isn't already aware of the bar event,                        // add it to the BarEventManager so that it shows up in bars                        if            (            DemoQuestCoordinator            .            shouldOfferQuest            ()            &&            !            barEventManager            .            hasEventCreator            (            DemoBarEventCreator            .            class            ))            {                        barEventManager            .            addEventCreator            (            new            DemoBarEventCreator            ());                        }                        }                        }          

All that's left is to add this to mod_info.json:

                        "modPlugin"            :            "your.class.package.DemoBaseModPlugin"          

and with that, Starsector will add the quest to bars around the sector! Of course, the code won't compile yet, because we still need to add the coordinator class.

Coordinator [ ]

A coordinator class handles all of the quest's logic and tracks the player's progress.

Note: This class isn't strictly necessary, as the logic it contains could instead be distributed throughout the rest of the quest's classes, but having one central class makes the quest code easier to understand and follow.

This class should be stateless, meaning that it has no field variables (unless they are read-only). Being stateless has a few advantages.

  • Ensures that the player's save game is the single source of truth for quest-related information.
  • Stateless classes also allow methods to be reused and combined more confidently.

Despite being stateless, the coordinator is able to track the player's quest progress. It does so by keeping state in the player's save file, rather than in the coordinator class, using either Global.getSector().getMemory() or Global.getSector().getPersistentData(). These work equally well, although persistent data is simpler.

Coordinator classes will not be automatically saved in the player's save game.

Here is a simple implementation for our Demo quest:

                                    /**                                      * Coordinates and tracks the state of Demo quest.                                      */                        class            DemoQuestCoordinator            {                        /**                                      * The tag that is applied to the planet the player must travel to.                                      */                        private            static            String            TAG_DESTINATION_PLANET            =            "Demo_destination_planet"            ;                                    static            SectorEntityToken            getDestinationPlanet            ()            {                        return            Global            .            getSector            ().            getEntityById            (            TAG_DESTINATION_PLANET            );                        }                                    static            boolean            shouldOfferQuest            ()            {                        return            true            ;            // Set some conditions                        }                                    /**                                      * Called when player starts the bar event.                                      */                        static            void            initQuest            ()            {                        chooseAndTagDestinationPlanet            ();                        }                                    /**                                      * Player has accepted quest.                                      */                        static            void            startQuest            ()            {                        Global            .            getSector            ().            getIntelManager            ().            addIntel            (            new            DemoIntel            ());                        }                                    /**                                      * Very dumb method that idempotently tags a random planet as the destination.                                      */                        private            static            void            chooseAndTagDestinationPlanet            ()            {                        if            (            getDestinationPlanet            ()            ==            null            )            {                        StarSystemAPI            randomSystem            =            Global            .            getSector            ().            getStarSystems            ()                        .            get            (            new            Random            ().            nextInt            (            Global            .            getSector            ().            getStarSystems            ().            size            ()));                        PlanetAPI            randomPlanet            =            randomSystem            .            getPlanets            ()                        .            get            (            new            Random            ().            nextInt            (            randomSystem            .            getPlanets            ().            size            ()));                        randomPlanet            .            addTag            (            TAG_DESTINATION_PLANET            );                        }                        }                        }          

Intel [ ]

Starsector's version of a quest log is the Intel Manager.

Intel must implement the IntelInfoPlugin, which is most easily done by creating a subclass of either BaseIntelPlugin or BreadcrumbIntel.

BaseIntelPlugin implements basic intel logic, but nothing more. Choose this for the most flexibility.

BreadcrumbIntel is a subclass of BaseIntelPlugin that adds support for pointing the player to a destination, and optionally showing a source destination and an arrow between them. Has some potentially unwanted default behavior, such as using the "Exploration" tag. Choose this to guide the player to some destination.

A piece of intel has a few different parts.

  • Icon: The quest icon. Vanilla icons are 40x40 px pngs.
  • Name: The current title of the quest, eg "Delivery - Completed". "Name" is part of BreadcrumbIntel, but not BaseIntelPlugin, which requires the title be added to the IntelInfo section manually.
  • Info: Text that appears underneath the name showing a quick objective or status, eg " - 45,000 reward".
  • Small Description: A bit of a misnomer, this is the description panel on the right side of the Intel Manager.
  • Intel Tags: The tag(s) that will appear in the Intel Manager and display this intel if selected.

Anatomy of Intel Manager

Here is our fairly barebones implementation ofBreadcrumbIntel

                                    public            class            DemoIntel            extends            BreadcrumbIntel            {                        public            DemoIntel            (            SectorEntityToken            foundAt            ,            SectorEntityToken            target            )            {                        super            (            foundAt            ,            target            );                        }                                    @Override                        public            String            getIcon            ()            {                        return            "graphics/icons/intel/player.png"            ;                        }                                    @Override                        public            String            getName            ()            {                        return            "Demo Quest"            +            (            DemoQuestCoordinator            .            isComplete            ()            ?            " - Completed"            :            ""            );                        }                                    /**                                      * The small list entry on the left side of the Intel Manager                                      *                                      * @param info the text area that shows the info                                      * @param mode where the info is being shown                                      */                        @Override                        public            void            createIntelInfo            (            TooltipMakerAPI            info            ,            ListInfoMode            mode            )            {                        // The call to super will add the quest name, so we just need to add the summary                        super            .            createIntelInfo            (            info            ,            mode            );                                    info            .            addPara            (            "Destination: %s"            ,            // text to show. %s is highlighted.                        3f            ,            // padding on left side of text. Vanilla hardcodes these values so we will too                        super            .            getBulletColorForMode            (            mode            ),            // color of text                        Misc            .            getHighlightColor            (),            // color of highlighted text                        DemoQuestCoordinator            .            getDestinationPlanet            ().            getName            ());            // highlighted text                                    // This will display in the intel manager like:                        // Demo Quest                        //     Destination: Ancyra                        }                                    @Override                        public            void            createSmallDescription            (            TooltipMakerAPI            info            ,            float            width            ,            float            height            )            {                        info            .            addImage            (            "graphics/illustrations/fly_away.jpg"            ,            // path to sprite                        width            ,                        128            ,            // height                        10            f            );            // left padding                                    info            .            addPara            (            "You learned a little about quest design at a bar on "            +            foundAt            .            getName            ()            +                        " and are traveling to %s to learn more."            ,            // text to show. %s is highlighted.                        10            f            ,            // padding on left side of text. Vanilla hardcodes these values so we will too                        Misc            .            getHighlightColor            (),            // color of highlighted text                        target            .            getName            ());            // highlighted text                                    // The super call adds the text from `getText()` (which we'll leave empty)                        // and then adds the number of days since the quest was acquired, which is                        // typically the bottom-most thing shown. Therefore, we'll make the call to                        // super as the last thing in this method.                        super            .            createSmallDescription            (            info            ,            width            ,            height            );                        }                                    /**                                      * Return whatever tags your quest should have. You can also create your own tags.                                      */                        @Override                        public            Set            <            String            >            getIntelTags            (            SectorMapAPI            map            )            {                        return            new            HashSet            <>            (            Arrays            .            asList            (            Tags            .            INTEL_EXPLORATION            ,            Tags            .            INTEL_STORY            ));                        }                        }          

Interaction Dialogs [ ]

An interaction dialog is simply a dialog window with options. Bar events are displayed using interaction dialogs; in order to display text, we used dialog.getTextPanel().addPara(text). dialog is a reference to the bar event's interaction dialog.

However, interaction dialogs can also be displayed on their own at any point, not just in bars, and we're going to leverage that to give the end of our demo quest a custom dialog that appears instead of the normal planet dialog.

There are two different interfaces/classes to choose from.

  • InteractionDialogPlugin is the interface for all interaction dialogs, making it the most flexible.
  • PaginatedOptions is great for presenting many options, such as a list of all factions. It displays "Next" and "Previous" as options. Check out the source of PaginatedOptionsExample for a basic example.

We don't need pages of options, so we'll implement this using InteractionDialogPlugin.

                                    public            class            DemoEndDialog            implements            InteractionDialogPlugin            {                        protected            InteractionDialogAPI            dialog            ;                                    /**                                      * Called when the dialog is shown.                                      *                                      * @param dialog the actual UI element being shown                                      */                        @Override                        public            void            init            (            InteractionDialogAPI            dialog            )            {                        // Save the dialog UI element so that we can write to it outside of this method                        this            .            dialog            =            dialog            ;                                    // Launch into our event by triggering the invisible "INIT" option,                        // which will call `optionSelected()`                        this            .            optionSelected            (            null            ,            DemoBarEvent            .            OptionId            .            INIT            );                        }                                    /**                                      * This method is called when the player has selected some option on the dialog.                                      *                                      * @param optionText the actual text that was displayed on the selected option                                      * @param optionData the value used to uniquely identify the option                                      */                        @Override                        public            void            optionSelected            (            String            optionText            ,            Object            optionData            )            {                        if            (            optionData            instanceof            OptionId            )            {                        // Clear shown options before we show new ones                        dialog            .            getOptionPanel            ().            clearOptions            ();                                    // Handle all possible options the player can choose                        switch            ((            OptionId            )            optionData            )            {                        // The invisible "init" option was selected by the init method.                        case            INIT            :                        dialog            .            getTextPanel            ().            addPara            (            "As soon as your shuttle touches down, your mind is filled "            +                        "with knowledge of how to create quest mods. How amazingly convenient."            );                        dialog            .            getTextPanel            ().            addPara            (            "You resolve to go off and create your very own!"            );                                    // The quest is completed as soon as the plot is resolved                        DemoQuestCoordinator            .            completeQuest            ();                                    dialog            .            getOptionPanel            ().            addOption            (            "Leave"            ,            OptionId            .            LEAVE            );                        break            ;                        case            LEAVE            :                        dialog            .            dismiss            ();                        break            ;                        }                        }                        }                                    enum            OptionId            {                        INIT            ,                        LEAVE                        }                                    // The rest of the methods must exist, but can be ignored for our simple demo quest.                        @Override                        public            void            optionMousedOver            (            String            optionText            ,            Object            optionData            )            {                        // Can add things like hints for what the option does here                        }                                    @Override                        public            void            advance            (            float            amount            )            {                        }                                    @Override                        public            void            backFromEngagement            (            EngagementResultAPI            battleResult            )            {                        }                                    @Override                        public            Object            getContext            ()            {                        return            null            ;                        }                                    @Override                        public            Map            <            String            ,            MemoryAPI            >            getMemoryMap            ()            {                        return            null            ;                        }                        }          


We now have a dialog that nearly wraps up the quest; all that's left is to show it to the player. For that, we'll create our own Campaign Plugin.

Showing a Dialog [ ]

To finish up our quest, we need to show a dialog when the player interacts with the destination planet.

There are two main options for doing this; CampaignPlugin and rules.csv.

  • BaseCampaignPlugin is the programmatic way that trades the complexity of rules.csv for the complexity of code. This method makes the quest mod arguably easier to understand, as there's less "magic"; any IDE will be able to see that an interaction dialog is being created in this class, whereas it's harder to determine how a dialog is being launched if it's done by rules.csv.
  • rules.csv; ah, rules.csv. Launching a dialog using this is the best choice for mod compatibility, as it will allow other modders to override your dialog launch trigger by the other mod using rules.csv.
    • To give an example, the Gates Awakened mod originally showed a dialog whenever the player interacted with a gate, triggered using a CampaignPlugin. However, Vayra's Sector had a special interaction that could occurr occasionally when the player interacted with a gate, triggered using rules.csv. Because the CampaignPlugin was always overriding anything in rules.csv, the Vayra's Sector interaction was never triggered as long as both mods were enabled. The fix was for Gates Awakened to trigger its dialog using rules.csv, and for Vayra's Sector to set the dialog trigger to a higher priority than the one in Gates Awakened.


The game chooses which interaction dialog to use to handle a player interaction by:

  1. First looking at all CampaignPlugins and seeing if any can handle the interaction.
  2. If multiple CampaignPlugins can handle the interaction, it chooses the one with the highest priority, as determined by PickPriority.
  3. If multiple plugins have the same priority, it chooses one somehow ("in an undefined way").
  4. If the plugin that is picked is RuleBasedInteractionDialogPluginImpl, then it will look at all possibilities in rules.csv and pick the one with the highest score.

Campaign Plugin [ ]

TODO

kaythintwit.blogspot.com

Source: https://starsector.fandom.com/wiki/Modding_Quests

0 Response to "Star Sector How to Continue Campaign"

Publicar un comentario

Iklan Atas Artikel

Iklan Tengah Artikel 1

Iklan Tengah Artikel 2

Iklan Bawah Artikel