Fractal Softworks Forum

Please login or register.

Login with username, password and session length
Advanced search  

News:

Starsector 0.97a is out! (02/02/24); New blog post: Simulator Enhancements (03/13/24)

Author Topic: Useful Java Code  (Read 39293 times)

LazyWizard

  • Global Moderator
  • Admiral
  • *****
  • Posts: 1363
    • View Profile
    • GitHub Profile
Useful Java Code
« on: August 18, 2012, 11:17:36 AM »

I thought it might be useful to have a thread that contains example code for commonly needed mod features. Eventually I hope to have a long list of working code samples for modders to play around with.

First up, I have two examples of how to use SpawnPointPlugin to create a mod control object. The first example runs events at timed intervals (each day, week, month, and year):

EventManager.java
Code
package data.scripts.world;

import com.fs.starfarer.api.campaign.LocationAPI;
import com.fs.starfarer.api.campaign.SectorAPI;
import com.fs.starfarer.api.campaign.SpawnPointPlugin;
import java.util.GregorianCalendar;

public class EventManager implements SpawnPointPlugin
{
    private static final float BASE_INTERVAL = 1.0f;
    private static final int FIRST_DAY_IN_WEEK = GregorianCalendar.SUNDAY;
    private float heartbeatInterval;
    private long lastHeartbeat;
    private SectorAPI sector;
    private GregorianCalendar calendar = new GregorianCalendar();

    public EventManager(SectorAPI sector)
    {
        // Synch the heartbeat to the sector clock
        this.sector = sector;
        lastHeartbeat = sector.getClock().getTimestamp();
        // The first heartbeat should happen at the start of day 1
        heartbeatInterval = (1.0f
                - (sector.getClock().getHour() / 24f));
    }

    private void runDaily()
    {
        sector.addMessage("Daily");
        // Insert code here
    }

    private void runWeekly()
    {
        sector.addMessage("Weekly");
        // Insert code here
    }

    private void runMonthly()
    {
        sector.addMessage("Monthly");
        // Insert code here
    }

    private void runYearly()
    {
        sector.addMessage("Yearly");
        // Insert code here
    }

    private void doIntervalChecks(long time)
    {
        lastHeartbeat = time;

        runDaily();

        calendar.setTimeInMillis(time);

        if (calendar.get(GregorianCalendar.DAY_OF_WEEK) == FIRST_DAY_IN_WEEK)
        {
            runWeekly();
        }

        if (calendar.get(GregorianCalendar.DAY_OF_MONTH) == 1)
        {
            runMonthly();

            if (calendar.get(GregorianCalendar.DAY_OF_YEAR) == 1)
            {
                runYearly();
            }
        }
    }

    private void checkSynched()
    {
        // Compensate for day-synch code in constructor
        if (heartbeatInterval != BASE_INTERVAL)
        {
            heartbeatInterval = BASE_INTERVAL;
        }
    }

    @Override
    public void advance(SectorAPI sector, LocationAPI location)
    {
        // Events that run at set in-game intervals
        if (sector.getClock().getElapsedDaysSince(lastHeartbeat) >= heartbeatInterval)
        {
            doIntervalChecks(sector.getClock().getTimestamp());
            checkSynched();
        }
    }
}

The second example contains two classes, and will allow you to set scripts to run at any time you wish (either by timestamp, calendar date, or a set time from now):

TimedScriptManager.java
Code
package data.scripts.world;

import com.fs.starfarer.api.Script;
import com.fs.starfarer.api.campaign.LocationAPI;
import com.fs.starfarer.api.campaign.SectorAPI;
import com.fs.starfarer.api.campaign.SpawnPointPlugin;
import java.util.*;

public class TimedScriptManager implements SpawnPointPlugin
{
    // If an added script has already passed its target runtime, should it run?
    private static final boolean RUN_SCRIPTS_IF_ADDED_LATE = false;
    private static final boolean SHOW_DEBUG_MESSAGES = false;
    // Helps convert between Starfarer and RL time
    private static final long STARFARER_SPEED = 8640000l;
    // Holds all TimedScriptManagers for easy lookup and (theoretical)
    // multi-sector support; get current Manager with getManager(SectorAPI)
    private static Map allManagers = Collections.synchronizedMap(new HashMap());
    // The sector the current object manages
    private SectorAPI sector;
    // This stores our scripts in a sorted set for efficient per-frame checks.
    // Sets don't allow duplicates, so the scripts are stored in a container
    // object that does (if there are two containers with the same timestamp,
    // mergeScripts will be called to add one container's scripts to the other)
    private SortedSet runOnceScripts = new TreeSet();

    public TimedScriptManager(SectorAPI sector)
    {
        // Don't add this to the list of managers if the sector has one already
        if (allManagers.keySet().contains(sector))
        {
            sector.addMessage("Duplicate TimedScriptManager detected!");
            return;
        }

        this.sector = sector;
        allManagers.put(sector, this);
    }

    // Returns the manager associated with a sector
    public static TimedScriptManager getManager(SectorAPI sector)
    {
        // If we have this sector registered, find the associated manager
        if (allManagers.keySet().contains(sector))
        {
            return (TimedScriptManager) allManagers.get(sector);
        }

        // No such TimedScriptManager
        return null;
    }

    // Runs the scripts at the provided timestamp
    public void addScriptsAtTimestamp(long timestamp, Script[] scripts)
    {
        // If the target time has already passed, run the scripts instantly
        if (timestamp <= sector.getClock().getTimestamp())
        {
            // .. unless you don't want to, that is
            if (!RUN_SCRIPTS_IF_ADDED_LATE)
            {
                showDebug("Scripts skipped due to lateness.");
                return;
            }

            showDebug("Running scripts early.");

            // Run the scripts
            for (int x = 0; x < scripts.length; x++)
            {
                scripts[x].run();
            }

            return;
        }

        // Create a container for the scripts (lazy optimization)
        TimedScriptContainer tmp = new TimedScriptContainer(timestamp);
        tmp.addScripts(scripts);

        // Is there a container with this target time already?
        if (runOnceScripts.contains(tmp))
        {
            showDebug("Debug: Two script containers with same timestamp!");

            // Sets don't allow duplicates, so merge the two containers
            mergeScripts(tmp);
            return;
        }

        runOnceScripts.add(tmp);
    }

    // Same as above, but with a single script
    public void addScriptAtTimestamp(long timestamp, Script script)
    {
        addScriptsAtTimestamp(timestamp, new Script[]
                {
                    script
                });
    }

    // Runs the scripts at the provided in-game date
    public void addScriptsAtDate(GregorianCalendar date, Script[] scripts)
    {
        addScriptsAtTimestamp(date.getTimeInMillis(), scripts);
    }

    // Same as above, but with a single script
    public void addScriptAtDate(GregorianCalendar date, Script script)
    {
        addScriptsAtDate(date, new Script[]
                {
                    script
                });
    }

    // Runs the scripts timeFromNow seconds after this is called
    public void addScripts(float timeFromNow, Script[] scripts)
    {
        // Adjust time to compensate for Starfarer game speed
        long timeToAdd = sector.getClock().getTimestamp();
        timeToAdd += (long) (timeFromNow * STARFARER_SPEED);

        addScriptsAtTimestamp(timeToAdd, scripts);
    }

    // Same as above, but with a single script
    public void addScript(float timeFromNow, Script script)
    {
        addScripts(timeFromNow, new Script[]
                {
                    script
                });
    }

    // Sets don't allow duplicate elements, so if a container already exists
    // with that timestamp, we must manually merge the scripts
    private void mergeScripts(TimedScriptContainer container)
    {
        Iterator iter = runOnceScripts.iterator();
        TimedScriptContainer tmp;

        // Find the container that matches this container's timestamp
        while (iter.hasNext())
        {
            tmp = (TimedScriptContainer) iter.next();

            // Found it! Now combine our scripts with it
            if (tmp.equals(container))
            {
                Script[] scripts = container.getScriptsAsArray();
                tmp.addScripts(scripts);

                showDebug("Debug: Merge successful!");

                return;
            }
        }

        // No matching container found (shouldn't ever happen)
        showDebug("Debug: Merge failed!");
    }

    // Runs once per frame while there is at least one script container
    private void checkScripts(long time)
    {
        Iterator iter = runOnceScripts.iterator();
        TimedScriptContainer tmp;
        Script script;

        // Iterate through the script containers
        while (iter.hasNext())
        {
            tmp = (TimedScriptContainer) iter.next();

            // As the list of scripts is sorted, we only need
            // to check until we've exceeded the current time
            if (tmp.targetTime > time)
            {
                // This is extremely spammy (1 line per frame)
                // Only uncomment if you have a problem you need to track down
                //debugMsg("Debug: Ignoring " + tmp.targetTime
                //        + " for " + (tmp.targetTime - time));

                break;
            }

            showDebug("Running " + tmp.targetTime + " (currently " + time + ")");

            // Run all scripts in the container
            for (int x = 0; x < tmp.scripts.size(); x++)
            {
                script = (Script) tmp.scripts.get(x);
                script.run();
            }

            showDebug("Removing script container.");

            // Remove container after it's been run
            iter.remove();
        }
    }

    private void showDebug(String text)
    {
        if (SHOW_DEBUG_MESSAGES)
        {
            sector.addMessage(text);
        }
    }

    @Override
    public void advance(SectorAPI sector, LocationAPI location)
    {
        // Only bother with the check if there are scripts to be run!
        if (!runOnceScripts.isEmpty())
        {
            checkScripts(sector.getClock().getTimestamp());
        }
    }
}

TimedScriptContainer.java
Code
package data.scripts.world;

import com.fs.starfarer.api.Script;
import java.util.*;

public class TimedScriptContainer implements Comparable
{
    public final long targetTime;
    protected ArrayList scripts;

    public TimedScriptContainer(long targetTime)
    {
        this.targetTime = targetTime;
        scripts = new ArrayList();
    }

    public void addScript(Script toAdd)
    {
        scripts.add(toAdd);
    }

    public void addScripts(Script[] toAdd)
    {
        scripts.addAll(Arrays.asList(toAdd));
    }

    // For now, there should be no way to modify scripts once set
    // I might change this later, when I have more time to debug
    public final List getScripts()
    {
        return Collections.unmodifiableList(scripts);
    }

    public final Script[] getScriptsAsArray()
    {
        return (Script[]) scripts.toArray(new Script[scripts.size()]);
    }

    // Prevents containers with duplicate targetTimes from being added to a set
    @Override
    public boolean equals(Object obj)
    {
        if (obj == null)
        {
            return false;
        }
        if (obj == this)
        {
            return true;
        }
        if (!(obj instanceof TimedScriptContainer))
        {
            return false;
        }
        TimedScriptContainer tmp = (TimedScriptContainer) obj;
        return (targetTime == tmp.targetTime);
    }

    // Used by the SortedSet in TimedScriptManager to sort the containers
    public int compareTo(Object obj)
    {
        TimedScriptContainer tmp = (TimedScriptContainer) obj;
        return ((Long) targetTime).compareTo((Long) tmp.targetTime);
    }
}

Here's a zip containing a mod using these examples: link


I'm also working on a mini-tutorial on advanced Script usage. That will probably come later, though. I have some Caelus stuff to work on. :)

While I'm cleaning up these scripts for general use, are there any other examples anyone wants me to include? If it's a code-related problem, I can probably post a prototype for you. :)

Inspiration: this post, which made me realize how non-obvious some of the solutions we take for granted are.
« Last Edit: August 18, 2012, 04:26:44 PM by LazyWizard »
Logged

LazyWizard

  • Global Moderator
  • Admiral
  • *****
  • Posts: 1363
    • View Profile
    • GitHub Profile
Re: Useful Java Code (in progress)
« Reply #1 on: August 18, 2012, 11:22:33 AM »

Before I post the code, though, does code inside of a spoiler tag look broken to everyone else, or is this just a Chrome display bug? It squashes the box into one line for me.

Spoiler
Code
Does
this
display
correctly
for
you?
[close]

If it's broken for others, I might just upload the files in a zip.
Logged

CrashToDesktop

  • Admiral
  • *****
  • Posts: 3876
  • Quartermaster
    • View Profile
Re: Useful Java Code (in progress)
« Reply #2 on: August 18, 2012, 11:24:07 AM »

Hmm, I see one box, using chrome.  There should be a little "drag corner" for it, like the "Quick Reply" box.
Logged
Quote from: Trylobot
I am officially an epoch.
Quote from: Thaago
Note: please sacrifice your goats responsibly, look up the proper pronunciation of Alex's name. We wouldn't want some other project receiving mystic power.

Thaago

  • Global Moderator
  • Admiral
  • *****
  • Posts: 7174
  • Harpoon Affectionado
    • View Profile
Re: Useful Java Code (in progress)
« Reply #3 on: August 18, 2012, 11:27:37 AM »

I see one line per word, using Firefox. What is it supposed to look like?
Logged

CrashToDesktop

  • Admiral
  • *****
  • Posts: 3876
  • Quartermaster
    • View Profile
Re: Useful Java Code (in progress)
« Reply #4 on: August 18, 2012, 11:28:47 AM »

Code
Code
Test
Code
Test
Code
Test
Code
Test
Having it not in a spoiler seems to work, at least in preview.
Logged
Quote from: Trylobot
I am officially an epoch.
Quote from: Thaago
Note: please sacrifice your goats responsibly, look up the proper pronunciation of Alex's name. We wouldn't want some other project receiving mystic power.

Alex

  • Administrator
  • Admiral
  • *****
  • Posts: 23986
    • View Profile
Re: Useful Java Code (in progress)
« Reply #5 on: August 18, 2012, 11:28:55 AM »

Before I post the code, though, does code inside of a spoiler tag look broken to everyone else, or is this just a Chrome display bug? It squashes the box into one line for me.

Spoiler
Code
Does
this
display
correctly
for
you?
[close]

If it's broken for others, I might just upload the files in a zip.

Looks squished in Chrome, fine in IE and Firefox.
Logged

Faiter119

  • Admiral
  • *****
  • Posts: 1402
    • View Profile
Re: Useful Java Code (in progress)
« Reply #6 on: August 18, 2012, 11:29:08 AM »

Looks good here at Oprah. 1 word per line.
Logged

CrashToDesktop

  • Admiral
  • *****
  • Posts: 3876
  • Quartermaster
    • View Profile
Re: Useful Java Code (in progress)
« Reply #7 on: August 18, 2012, 11:33:50 AM »

Damn it, I have to get out of Chrome and go into IE9 if I want to use his code. ;D Making a new faction, never hurts to use new stuff. :)
Logged
Quote from: Trylobot
I am officially an epoch.
Quote from: Thaago
Note: please sacrifice your goats responsibly, look up the proper pronunciation of Alex's name. We wouldn't want some other project receiving mystic power.

acidzombie

  • Commander
  • ***
  • Posts: 127
  • Random Guy61
    • View Profile
Re: Useful Java Code (in progress)
« Reply #8 on: August 18, 2012, 01:06:20 PM »

Or use RockMelt there it works if it isnt in a spoiler
Logged
What is worth dying for is for you to choose.

LazyWizard

  • Global Moderator
  • Admiral
  • *****
  • Posts: 1363
    • View Profile
    • GitHub Profile
Re: Useful Java Code (in progress)
« Reply #9 on: August 18, 2012, 01:15:28 PM »

Sorry this took so long, I spent half an hour trying to get addScriptAtDate working before I realized my test case was using the year 206 instead of 205.  :-[

Here's what I have so far for the arbitrary script manager. I probably made it more complex than I should have, but hopefully it's still understandable. :)

I'll clean it up a bit more, then add it to the main post once I get the interval event manager working as well.

TimedScriptManager.java
Code
package data.scripts.world;

import com.fs.starfarer.api.Global;
import com.fs.starfarer.api.Script;
import com.fs.starfarer.api.campaign.LocationAPI;
import com.fs.starfarer.api.campaign.SectorAPI;
import com.fs.starfarer.api.campaign.SpawnPointPlugin;
import java.util.*;

public class TimedScriptManager implements SpawnPointPlugin
{
    // If an added script has already passed its target runtime, should it run?
    private static final boolean RUN_SCRIPTS_IF_ADDED_LATE = false;
    private static final boolean SHOW_DEBUG_MESSAGES = false;
    // Helps convert between Starfarer and RL time
    private static final long STARFARER_SPEED = 8640000l;
    // Holds all TimedScriptManagers for easy lookup and (theoretically)
    // multi-sector support, get current Manager with getManager(SectorAPI)
    private static Map allManagers = Collections.synchronizedMap(new HashMap());
    // The sector the current object manages
    private SectorAPI sector;
    // This stores our scripts in a sorted set for efficient per-frame checks.
    // Sets don't allow duplicates, so the scripts are stored in a container
    // object that does (if there are two containers with the same timestamp,
    // mergeScripts will be called to add one container's scripts to the other)
    private SortedSet runOnceScripts = new TreeSet();

    public TimedScriptManager(SectorAPI sector)
    {
        // Don't add this to the list of managers if the sector has one already
        if (allManagers.keySet().contains(sector))
        {
            sector.addMessage("Duplicate TimedScriptManager detected!");
            return;
        }

        allManagers.put(sector, this);
    }

    // Returns the manager associated with a sector
    public static TimedScriptManager getManager(SectorAPI sector)
    {
        // If we have this sector registered, find the associated manager
        if (allManagers.keySet().contains(sector))
        {
            return (TimedScriptManager) allManagers.get(sector);
        }

        // No such TimedScriptManager
        return null;
    }

    // Runs the scripts at the provided timestamp
    public void addScriptsAtTimestamp(long timestamp, Script[] scripts)
    {
        // If the target time has already passed, run the scripts instantly
        if (timestamp <= Global.getSector().getClock().getTimestamp())
        {
            // .. unless you don't want to, that is
            if (!RUN_SCRIPTS_IF_ADDED_LATE)
            {
                return;
            }

            showDebug("Running scripts early.");

            // Run the scripts
            for (int x = 0; x < scripts.length; x++)
            {
                scripts[x].run();
            }

            return;
        }

        // Create a container for the scripts (lazy optimization)
        TimedScriptContainer tmp = new TimedScriptContainer(timestamp);
        tmp.addScripts(scripts);

        // Is there a container with this target time already?
        if (runOnceScripts.contains(tmp))
        {
            showDebug("Debug: Two script containers with same timestamp!");

            // Sets don't allow duplicates, so merge the two containers
            mergeScripts(tmp);
            return;
        }

        runOnceScripts.add(tmp);
    }

    // Same as above, but with a single script
    public void addScriptAtTimestamp(long timestamp, Script script)
    {
        addScriptsAtTimestamp(timestamp, new Script[]
                {
                    script
                });
    }

    // Runs the scripts at the provided in-game date
    public void addScriptsAtDate(GregorianCalendar date, Script[] scripts)
    {
        addScriptsAtTimestamp(date.getTimeInMillis(), scripts);
    }

    // Same as above, but with a single script
    public void addScriptAtDate(GregorianCalendar date, Script script)
    {
        addScriptsAtDate(date, new Script[]
                {
                    script
                });
    }

    // Runs the scripts timeFromNow seconds after this is called
    public void addScripts(float timeFromNow, Script[] scripts)
    {
        // Adjust time to compensate for Starfarer game speed
        long timeToAdd = Global.getSector().getClock().getTimestamp();
        timeToAdd += (long) (timeFromNow * STARFARER_SPEED);

        addScriptsAtTimestamp(timeToAdd, scripts);
    }

    // Same as above, but with a single script
    public void addScript(float timeFromNow, Script script)
    {
        addScripts(timeFromNow, new Script[]
                {
                    script
                });
    }

    //<editor-fold defaultstate="collapsed" desc="Private methods">
    // Sets don't allow duplicate elements, so if a container already exists
    // with that timestamp, we must manually merge the scripts
    private void mergeScripts(TimedScriptContainer container)
    {
        Iterator iter = runOnceScripts.iterator();
        TimedScriptContainer tmp;

        // Find the container that matches this container's timestamp
        while (iter.hasNext())
        {
            tmp = (TimedScriptContainer) iter.next();

            // Found it! Now combine our scripts with it
            if (tmp.equals(container))
            {
                Script[] scripts = container.getScriptsAsArray();
                tmp.addScripts(scripts);

                showDebug("Debug: Merge successful!");

                return;
            }
        }

        // No matching container found (shouldn't ever happen)
        showDebug("Debug: Merge failed!");
    }

    // Runs once per frame while there is at least one script container
    private void checkScripts(long time)
    {
        Iterator iter = runOnceScripts.iterator();
        TimedScriptContainer tmp;
        Script script;

        // Iterate through the script containers
        while (iter.hasNext())
        {
            tmp = (TimedScriptContainer) iter.next();

            // As the list of scripts is sorted, we only need
            // to check until we've exceeded the current time
            if (tmp.targetTime > time)
            {
                // This is extremely spammy (1 line per frame)
                // Only uncomment if you have a problem you need to track down
                //debugMsg("Debug: Ignoring " + tmp.targetTime
                //        + " for " + (tmp.targetTime - time));

                break;
            }

            showDebug("Running " + tmp.targetTime + " (currently " + time + ")");

            // Run all scripts in the container
            for (int x = 0; x < tmp.scripts.size(); x++)
            {
                script = (Script) tmp.scripts.get(x);
                script.run();
            }

            showDebug("Removing script container.");

            // Remove run containers
            iter.remove();
        }
    }
    //</editor-fold>

    public void advance(SectorAPI sector, LocationAPI location)
    {
        // Only bother with the check if there are scripts to be run!
        if (!runOnceScripts.isEmpty())
        {
            checkScripts(Global.getSector().getClock().getTimestamp());
        }
    }

    private void showDebug(String text)
    {
        if (SHOW_DEBUG_MESSAGES)
        {
            Global.getSector().addMessage(text);
        }
    }
}

TimedScriptContainer.java
Code
package data.scripts.world;

import com.fs.starfarer.api.Script;
import java.util.*;

public class TimedScriptContainer implements Comparable
{
    public final long targetTime;
    protected ArrayList scripts;

    public TimedScriptContainer(long targetTime)
    {
        this.targetTime = targetTime;
        scripts = new ArrayList();
    }

    public void addScript(Script toAdd)
    {
        scripts.add(toAdd);
    }

    public void addScripts(Script[] toAdd)
    {
        scripts.addAll(Arrays.asList(toAdd));
    }

    // For now, there should be no way to modify scripts once set
    // I might change this later, when I have more time to debug
    public final List getScripts()
    {
        return Collections.unmodifiableList(scripts);
    }

    public final Script[] getScriptsAsArray()
    {
        return (Script[]) scripts.toArray(new Script[scripts.size()]);
    }

    @Override
    public boolean equals(Object obj)
    {
        if (obj == null)
        {
            return false;
        }
        if (obj == this)
        {
            return true;
        }
        if (!(obj instanceof TimedScriptContainer))
        {
            return false;
        }
        TimedScriptContainer tmp = (TimedScriptContainer) obj;
        return (targetTime == tmp.targetTime);
    }

    public int compareTo(Object obj)
    {
        TimedScriptContainer tmp = (TimedScriptContainer) obj;
        return ((Long) targetTime).compareTo((Long) tmp.targetTime);
    }
}

The Corvus.java I used for testing:
Code
package data.scripts.world.corvus;

import com.fs.starfarer.api.Global;
import com.fs.starfarer.api.Script;
import com.fs.starfarer.api.campaign.SectorAPI;
import com.fs.starfarer.api.campaign.SectorGeneratorPlugin;
import com.fs.starfarer.api.campaign.StarSystemAPI;
import data.scripts.world.TimedScriptManager;
import java.util.*;

@SuppressWarnings("unchecked")
public class Corvus implements SectorGeneratorPlugin
{
    private class MessageScript implements Script
    {
        String message;

        public MessageScript(String message)
        {
            this.message = message;
        }

        public void run()
        {
            Global.getSector().addMessage(message);
        }
    }

    public void generate(SectorAPI sector)
    {
        StarSystemAPI system = sector.getStarSystem("Corvus");

        TimedScriptManager scriptManager = new TimedScriptManager(sector);
        system.addSpawnPoint(scriptManager);
        long time = sector.getClock().getTimestamp();

        // Test adding scripts
        scriptManager.addScriptAtTimestamp((time + 4000l),
                new MessageScript("Script success!"));

        // Test duplicate timestamp merging
        scriptManager.addScriptAtTimestamp((time + 400000l),
                new MessageScript("Merged script 1 success!"));
        scriptManager.addScriptAtTimestamp((time + 400000l),
                new MessageScript("Merged script 2 success!"));

        // Test adding multiple scripts at once
        scriptManager.addScriptsAtTimestamp((time + 99999999),
                new Script[]
                {
                    new MessageScript("Script 1 success!"),
                    new MessageScript("Script 2 success!"),
                    new MessageScript("Script 3 success!")
                });

        // Test adding scripts by RL time
        scriptManager.addScript(5f,
                new MessageScript("Script should have run 5 seconds after game start."));
        scriptManager.addScript(10f,
                new MessageScript("Script should have run 10 seconds after game start."));
        scriptManager.addScript(15f,
                new MessageScript("Script should have run 15 seconds after game start."));

        // Test adding scripts that run on a certain date
        scriptManager.addScriptAtDate(new GregorianCalendar(205, 11, 2),
                new MessageScript("Script should run December 2!"));

        // Test Manager lookup code
        TimedScriptManager test = TimedScriptManager.getManager(sector);
        test.addScript(20f,
                new MessageScript("Script should have run 20 seconds after game start."));
    }
}
Logged

Thaago

  • Global Moderator
  • Admiral
  • *****
  • Posts: 7174
  • Harpoon Affectionado
    • View Profile
Re: Useful Java Code (in progress)
« Reply #10 on: August 18, 2012, 02:02:41 PM »

Very nicely done! It was useful for my learning of Java to read through this, so thanks.
Logged

LazyWizard

  • Global Moderator
  • Admiral
  • *****
  • Posts: 1363
    • View Profile
    • GitHub Profile
Re: Useful Java Code (in progress)
« Reply #11 on: August 18, 2012, 04:13:26 PM »

This controller is simple compared to the last one, so I didn't comment it nearly as much. It runs events at the start of each day, week, month, and year.

EventManager.java
Code
package data.scripts.world;

import com.fs.starfarer.api.campaign.LocationAPI;
import com.fs.starfarer.api.campaign.SectorAPI;
import com.fs.starfarer.api.campaign.SpawnPointPlugin;
import java.util.GregorianCalendar;

public class EventManager implements SpawnPointPlugin
{
    private static final float BASE_INTERVAL = 1.0f;
    private static final int FIRST_DAY_IN_WEEK = GregorianCalendar.SUNDAY;
    private float heartbeatInterval;
    private long lastHeartbeat;
    private SectorAPI sector;
    private GregorianCalendar calendar = new GregorianCalendar();

    public EventManager(SectorAPI sector)
    {
        // Synch the heartbeat to the sector clock
        this.sector = sector;
        lastHeartbeat = sector.getClock().getTimestamp();
        // The first heartbeat should happen at the start of day 1
        heartbeatInterval = (1.0f
                - (sector.getClock().getHour() / 24f));
    }

    private void runDaily()
    {
        sector.addMessage("Daily");
        // Insert code here
    }

    private void runWeekly()
    {
        sector.addMessage("Weekly");
        // Insert code here
    }

    private void runMonthly()
    {
        sector.addMessage("Monthly");
        // Insert code here
    }

    private void runYearly()
    {
        sector.addMessage("Yearly");
        // Insert code here
    }

    private void doIntervalChecks(long time)
    {
        lastHeartbeat = time;

        runDaily();

        calendar.setTimeInMillis(time);

        if (calendar.get(GregorianCalendar.DAY_OF_WEEK) == FIRST_DAY_IN_WEEK)
        {
            runWeekly();
        }

        if (calendar.get(GregorianCalendar.DAY_OF_MONTH) == 1)
        {
            runMonthly();

            if (calendar.get(GregorianCalendar.DAY_OF_YEAR) == 1)
            {
                runYearly();
            }
        }
    }

    private void checkSynched()
    {
        // Compensate for day-synch code in constructor
        if (heartbeatInterval != BASE_INTERVAL)
        {
            heartbeatInterval = BASE_INTERVAL;
        }
    }

    @Override
    public void advance(SectorAPI sector, LocationAPI location)
    {
        // Events that run at set in-game intervals
        if (sector.getClock().getElapsedDaysSince(lastHeartbeat) >= heartbeatInterval)
        {
            doIntervalChecks(sector.getClock().getTimestamp());
            checkSynched();
        }
    }
}
}
« Last Edit: August 18, 2012, 04:15:53 PM by LazyWizard »
Logged

Verrius

  • Captain
  • ****
  • Posts: 369
    • View Profile
Re: Useful Java Code
« Reply #12 on: August 18, 2012, 09:58:26 PM »

Inspiration: this post, which made me realize how non-obvious some of the solutions we take for granted are.
Hehe :p

Thanks for this, I'm sure I'll find some way to make wonderful use of it in another feature mod.