Starsector Nihilism Guidance
首页
GitHub 项目
首页
GitHub 项目
  • 首页
  • 基础及舰船相关
    • MOD 骨架和入口文件
    • 制作一艘基本舰船
    • 制作一艘航母与战机
    • 制作一艘相位舰
    • 制作火炮、光束与导弹
    • 为舰船加入内置武器、船插与战机
    • 补充描述文本
  • 无需入门的代码修改
    • 以船插为例初步熟悉代码
    • 制作完整船插
    • 制作最小战术系统
    • 为武器加入命中与光束特效
  • 势力与生涯星系
    • 注册一个势力
    • 配置势力舰队
    • 制作 MOD 入口类的最基础适配
    • 制作一个生涯星系
    • 补完描述文本
  • 战役与无代码对象
    • 制作测试战役装配
    • 为模拟战与标题画面加入装配
    • 为舰船加入外观风格
    • 制作一艘模块船
    • 制作一种星体类型
    • 制作一个自定义实体
  • 音频资源与播放
    • 准备 OGG 音频资源
    • 注册音频资源
    • 为游戏对象加入音频
  • Java 工程化
    • 确认 Java 与编译依赖
    • 迁移代码文件到 src
    • 配置 Gradle 工程
    • 构建 JAR 并声明产物
  • 全局服务
    • 制作 MOD 入口类
    • 使用 Global 访问游戏服务
    • 读取设置
    • 调用声音播放器
    • 记录和定位 MOD 日志
  • 战斗运行时
    • 读取战斗引擎状态
    • 读取战斗对象
    • 制作每帧战斗插件
    • 修改舰船数值
    • 配置武器脚本
    • 配置战斗监听器
    • 制作分层渲染插件
  • 战斗内容扩展
    • 详解舰船插件
    • 详解战术系统
    • 详解武器动画
  • 生涯世界与基础服务
    • 读取生涯世界状态
    • 构建生涯地点
    • 配置生涯势力
    • 放置生涯实体
  • 生涯舰队与舰队成员
    • 生成生涯舰队
    • 管理舰队中的成员
    • 舰队状态的重算过程
    • 维护舰队成员状态
    • 读取并修改舰船装配
  • 市场与商品
    • 创建生涯市场
    • 读写货舱内容
    • 注册市场商品
    • 实现专用子市场
    • 制作专用产业
    • 注册特殊物品
    • 实现市场条件
  • 人物与技能
    • 配置人物基础
    • 让人物承担角色
    • 制作角色技能
  • 生涯脚本与情报事件
    • 编写生涯逐帧脚本
    • 监听生涯事件
    • 选择生涯插件
    • 管理舰队活动情报
  • 酒吧事件与任务
    • 组织生涯交互对话
    • 实现对话插件
    • 接入酒吧事件
    • 构建阶段任务
    • 接入酒吧任务
  • Rules 系统、命令与内存
    • 组织 Rules 规则表
    • 编排 Rules 对话
    • 接入 Rules 生涯场景
    • 编写 Rules 命令
    • 使用 MemoryAPI 保存状态
  • 文件读取与写入
    • 读取 MOD 内文本
    • 读取 MOD 内 JSON 文件
    • 读取 MOD 内 CSV
    • 读写 common 数据
    • 实现本地化机制
  • 常用依赖库
    • 接入常用依赖库
    • 使用 LazyLib 几何工具
    • 使用 LazyLib 查询战场
    • 使用 LazyLib 保存公共 JSON
    • 注册 GraphicsLib 光照数据
    • 创建 GraphicsLib 动态光源
    • 制作 GraphicsLib 纹理映射
    • 配置 MagicLib 自动尾迹
    • 创建 MagicLib 运行时尾迹
    • 配置 MagicLib 数据赏金
    • 使用 MagicLib 辅助生涯开发
    • 使用 MagicLib 战斗小工具
  • 可选依赖与 Nexerelin
    • 接入可选依赖
    • 登记 Nexerelin 势力与外交配置
    • 配置 Nexerelin 的两种星区开局
    • 配置 Nexerelin 战略 AI 与扩张规模
    • 登记 Nexerelin 地面战单位与产业
    • 为 Nexerelin 特遣队添加命名与事件响应
    • 制作即时生效的 LunaLib 设置面板
  • 着色器与渲染
    • 使用 Java OpenGL
    • 使用 GLSL 进阶
    • 使用 VBO 和 VAO 保存顶点
    • 使用 SSBO 传递参数
    • 使用实例化进行绘制
    • 选择 BoxUtil 渲染方案

制作即时生效的 LunaLib 设置面板

本节让 LunaLib 的开关、数量、间隔、颜色和标签进入实际战斗显示。控件负责保存,读取器取得当前值,战斗插件在自己的调用时机使用它们;变更监听另外记录通知。设置保存后无需重新构建代码,库缺席时同一个插件采用代码缺省。

本节预期阅读时间:~18 分钟。

本节预期操作时间:~30 分钟。

学习目标

完成后,您将能够:

  • 按九列 CSV 定义开关、整数、浮点数、颜色和文本;
  • 用包装类型读取字段,并处理库缺席和缺失值;
  • 登记一次变更监听,按 MOD ID 过滤通知;
  • 将设置接到战斗标记与标签,观察保存后的实际结果。

前置知识

  • [C19-S1] 的 LunaLib 编译依赖、拥有者 ID 和可选适配;
  • [C8-S3] 的战斗插件、暂停和 settings 登记;
  • [C18-S3] 的附近敌舰查询;
  • [C1-S7] 的 CSV 空列与转义规则。

在现有设置表追加字段

打开 data/config/LunaSettings.csv,保留 [C19-S1] 的 GearIn_fullGraphicEffect,追加本节五行。完整输入为:

fieldID,fieldName,fieldType,defaultValue,secondaryValue,fieldDescription,minValue,maxValue,tab
GearIn_fullGraphicEffect,Full graphic effect,Boolean,true,,,,,GearIn
GearIn_trails_enabled,Show GearIn trails,Boolean,true,,,,,GearIn
GearIn_max_entries,Maximum entries,Int,12,,,1,64,GearIn
GearIn_pulse_delay,Pulse delay,Double,1.5,,,0.1,10,GearIn
GearIn_marker_color,Marker color,Color,#f22e6e,,,,,GearIn
GearIn_label_text,Label text,String,GearIn,,,,,GearIn

**字段 ID、控件类型和读取方法对应同一份设置。**CSV 按来源 MOD 记录拥有者,Java 再以 gear_in 和字段 ID 读取;空列保留位置,文本中含英文逗号时按 CSV 转义。

fieldID、fieldName、fieldType 与 defaultValue 参与控件创建,描述、次值、范围和 tab 各自按用途读取。类型为 Int 或 Double 时,面板使用范围组织输入;getter 取得已保存值,当前代码的缺省只处理缺席或缺失。

设置与画面速查表

字段 ID类型与缺省当前使用方
GearIn_trails_enabledBoolean,true控制附近敌舰标记生成
GearIn_max_entriesInt,12,1–64限制一次标记的目标数
GearIn_pulse_delayDouble,1.5,0.1–10控制两次标记的间隔秒数
GearIn_marker_colorColor,#f22e6e标记颜色
GearIn_label_textString,GearIn玩家状态标签文字

本节定义 GearIn_trails_enabled,当前插件用短寿命粒子提供观察结果。需要接到尾迹效果时,同一个布尔值也可以决定是否追加节点,实际使用由效果类安排。

保存定义和读取器

将 Defs、Reader、Listener 完整类保存到 src/data/scripts/gearin/lib/。Defs 保存键和代码默认,Reader 各方法先查询启用状态,再用对应包装类型取值。局部调用如下:

Boolean enabled = LunaSettings.getBoolean("gear_in", "GearIn_trails_enabled");
Integer entries = LunaSettings.getInt("gear_in", "GearIn_max_entries");
Float delay = LunaSettings.getFloat("gear_in", "GearIn_pulse_delay");
Color color = LunaSettings.getColor("gear_in", "GearIn_marker_color");
String label = LunaSettings.getString("gear_in", "GearIn_label_text");

LunaLib 缺少 MOD、字段或不能取得所需类型时,getter 记录错误并返回 null,Reader 返回 Defs 缺省。Double 配置通过 getFloat 转成浮点秒数,Color 从十六进制文本取得 RGB,String 保留保存的文字。

首次 getter 可以触发库加载设置,后续从已加载数据取值。值更新以后,使用方的下一次读取才能改变行为。 保存通知可以处理缓存更新或其他工作,本例的监听器用于记录字段值。

编者在这个示例选择直接读取:这些参数改变下一次标签显示与粒子生成,无需重新建立战斗插件。若设置改变涉及纹理预载等较重操作,再把对应重建安排到通知中,职责和代价会更明确。

将监听器接到生涯加载

在既有 GearInModPlugin.onGameLoad(...) 追加下面的局部调用,导入 Reader 与 Listener,原有初始化继续保留:

if (GearInLunaSettingsReader.isLunaLibEnabled()) {
    GearInLunaSettingsListener.registerListener();
    GearInLunaSettingsListener.logCurrentValues();
}

Listener 的外层只使用 Java 和自有类型,第三方接口字段与实例留在嵌套 LunaAdapter。启用分支通过后才调用适配器;同类监听已经存在时结束登记,读档继续使用应用级注册。

面板保存实际修改的字段后,按拥有者分别更新值并发送通知。settingsChanged(modId) 只处理 gear_in,输出 reloaded 和当前字段;unregisterListener() 移除本例实例,之后再注册会重新建立。

新档设置面板不会发送这项通知

新档创建过程中的面板保存会更新数据,但跳过 settingsChanged。本节在进入生涯后登记监听,并通过正常生涯的保存操作观察回调。

让五个字段进入战斗

保存完整 GearInSettingsVisualPlugin 到 src/data/scripts/gearin/combat/,在已有 data/config/settings.json 的 plugins 对象追加:

"GearIn_settings_visual": "data.scripts.gearin.combat.GearInSettingsVisualPlugin"

插件在每个有效帧读取五项设置,状态标签显示文字、开关、上限与间隔。开关开启时累积非暂停时间,到达阈值后查询与玩家碰撞圆间隔不超过 1600 su 的可见敌舰,再按集合顺序标记前若干目标。数量采用配置上限与候选数中的较小者,24 su 粒子持续 0.4 秒。

关闭开关会清零计时,新标记停止;已经生成的粒子继续结束自己的寿命。保存颜色后,下一组粒子采用新颜色;保存文字后,下一有效帧更新标签。改变间隔时,当前已经累计的时间与新阈值继续比较。

完整最小例

配置采用前面的 CSV 与战斗登记,四个类分别承担常量、读取、通知与实际效果:

完整成品:GearInLunaSettingsDefs
package data.scripts.gearin.lib;

import java.awt.Color;

public final class GearInLunaSettingsDefs {
    public static final String MOD_ID = "gear_in";
    public static final String LUNA_LIB_MOD_ID = "lunalib";

    public static final String KEY_TRAILS = "GearIn_trails_enabled";
    public static final String KEY_MAX_ENTRIES = "GearIn_max_entries";
    public static final String KEY_PULSE_DELAY = "GearIn_pulse_delay";
    public static final String KEY_MARKER_COLOR = "GearIn_marker_color";
    public static final String KEY_LABEL_TEXT = "GearIn_label_text";

    public static final boolean DEFAULT_TRAILS = true;
    public static final int DEFAULT_MAX_ENTRIES = 12;
    public static final float DEFAULT_PULSE_DELAY = 1.5f;
    public static final Color DEFAULT_MARKER_COLOR = new Color(0xf2, 0x2e, 0x6e);
    public static final String DEFAULT_LABEL_TEXT = "GearIn";

    private GearInLunaSettingsDefs() {
    }
}
完整成品:GearInLunaSettingsReader
package data.scripts.gearin.lib;

import java.awt.Color;

import com.fs.starfarer.api.Global;

import lunalib.lunaSettings.LunaSettings;

/** 按启用状态读取设置,缺失值使用已定义的缺省。 */
public class GearInLunaSettingsReader {

    private GearInLunaSettingsReader() {
    }

    /** 查询 LunaLib 的启用状态。 */
    public static boolean isLunaLibEnabled() {
        return Global.getSettings().getModManager().isModEnabled(GearInLunaSettingsDefs.LUNA_LIB_MOD_ID);
    }

    /** 轨迹特效开关:Boolean 设置,缺省 true。 */
    public static boolean isTrailsEnabled() {
        if (!isLunaLibEnabled()) {
            return GearInLunaSettingsDefs.DEFAULT_TRAILS;
        }
        Boolean value = LunaSettings.getBoolean(GearInLunaSettingsDefs.MOD_ID, GearInLunaSettingsDefs.KEY_TRAILS);
        return value != null ? value : GearInLunaSettingsDefs.DEFAULT_TRAILS;
    }

    /** 面板最大条目数:Int 设置,缺省 12。 */
    public static int getMaxEntries() {
        if (!isLunaLibEnabled()) {
            return GearInLunaSettingsDefs.DEFAULT_MAX_ENTRIES;
        }
        Integer value = LunaSettings.getInt(GearInLunaSettingsDefs.MOD_ID, GearInLunaSettingsDefs.KEY_MAX_ENTRIES);
        return value != null ? value : GearInLunaSettingsDefs.DEFAULT_MAX_ENTRIES;
    }

    /** 脉冲间隔 (秒):CSV 里是 Double 类型,读取时用 getFloat (从保存的 double 转换)。 */
    public static float getPulseDelay() {
        if (!isLunaLibEnabled()) {
            return GearInLunaSettingsDefs.DEFAULT_PULSE_DELAY;
        }
        Float value = LunaSettings.getFloat(GearInLunaSettingsDefs.MOD_ID, GearInLunaSettingsDefs.KEY_PULSE_DELAY);
        return value != null ? value : GearInLunaSettingsDefs.DEFAULT_PULSE_DELAY;
    }

    /** 标记颜色:Color 设置,保存/读取十六进制字符串 (# 前缀可省略)。 */
    public static Color getMarkerColor() {
        if (!isLunaLibEnabled()) {
            return GearInLunaSettingsDefs.DEFAULT_MARKER_COLOR;
        }
        Color value = LunaSettings.getColor(GearInLunaSettingsDefs.MOD_ID, GearInLunaSettingsDefs.KEY_MARKER_COLOR);
        return value != null ? value : GearInLunaSettingsDefs.DEFAULT_MARKER_COLOR;
    }

    /** 标签文本:String 设置,按原样读取。 */
    public static String getLabelText() {
        if (!isLunaLibEnabled()) {
            return GearInLunaSettingsDefs.DEFAULT_LABEL_TEXT;
        }
        String value = LunaSettings.getString(GearInLunaSettingsDefs.MOD_ID, GearInLunaSettingsDefs.KEY_LABEL_TEXT);
        return value != null ? value : GearInLunaSettingsDefs.DEFAULT_LABEL_TEXT;
    }
}
完整成品:GearInLunaSettingsListener
package data.scripts.gearin.lib;

import java.awt.Color;

import org.apache.log4j.Logger;

import com.fs.starfarer.api.Global;

import lunalib.lunaSettings.LunaSettings;
import lunalib.lunaSettings.LunaSettingsListener;

/** 登记设置变更通知,并记录本 MOD 当前生效值。 */
public class GearInLunaSettingsListener {
    private static final Logger LOG = Global.getLogger(GearInLunaSettingsListener.class);

    private GearInLunaSettingsListener() {
    }

    public static void registerListener() {
        if (!GearInLunaSettingsReader.isLunaLibEnabled()) {
            return;
        }
        LunaAdapter.register();
    }

    public static void unregisterListener() {
        if (!GearInLunaSettingsReader.isLunaLibEnabled()) {
            return;
        }
        LunaAdapter.unregister();
    }

    public static void logCurrentValues() {
        LOG.info("GearIn luna settings: trails=" + GearInLunaSettingsReader.isTrailsEnabled()
                + " maxEntries=" + GearInLunaSettingsReader.getMaxEntries()
                + " pulseDelay=" + GearInLunaSettingsReader.getPulseDelay()
                + " markerColor=" + toHex(GearInLunaSettingsReader.getMarkerColor())
                + " labelText=" + GearInLunaSettingsReader.getLabelText());
    }

    private static String toHex(Color color) {
        return String.format("#%02x%02x%02x", color.getRed(), color.getGreen(), color.getBlue());
    }

    private static final class LunaAdapter {
        private static LunaSettingsListener registered;

        private static void register() {
            if (registered != null && LunaSettings.hasSettingsListenerOfClass(registered.getClass())) {
                return;
            }
            registered = new LunaSettingsListener() {
                @Override
                public void settingsChanged(String modId) {
                    if (!GearInLunaSettingsDefs.MOD_ID.equals(modId)) {
                        return;
                    }
                    LOG.info("GearIn luna settings: reloaded");
                    logCurrentValues();
                }
            };
            LunaSettings.addSettingsListener(registered);
            LOG.info("GearIn luna settings: listener registered");
        }

        private static void unregister() {
            if (registered != null) {
                LunaSettings.removeSettingsListener(registered);
                registered = null;
            }
        }
    }
}
完整成品:GearInSettingsVisualPlugin
package data.scripts.gearin.combat;

import java.awt.Color;
import java.util.List;

import com.fs.starfarer.api.combat.BaseEveryFrameCombatPlugin;
import com.fs.starfarer.api.combat.CombatEngineAPI;
import com.fs.starfarer.api.combat.ShipAPI;
import com.fs.starfarer.api.input.InputEventAPI;
import data.scripts.gearin.lib.GearInLunaSettingsReader;
import org.lazywizard.lazylib.combat.AIUtils;
import org.lwjgl.util.vector.Vector2f;

/** 使用当前设置生成附近敌舰标记和玩家状态标签。 */
public final class GearInSettingsVisualPlugin extends BaseEveryFrameCombatPlugin {
    private CombatEngineAPI engine;
    private float elapsed;

    @Override
    public void init(CombatEngineAPI engine) {
        this.engine = engine;
    }

    @Override
    public void advance(float amount, List<InputEventAPI> events) {
        if (engine.isPaused()) {
            return;
        }
        ShipAPI player = engine.getPlayerShip();
        if (player == null || !player.isAlive() || !engine.isEntityInPlay(player)) {
            return;
        }

        boolean enabled = GearInLunaSettingsReader.isTrailsEnabled();
        int maxEntries = GearInLunaSettingsReader.getMaxEntries();
        float delay = GearInLunaSettingsReader.getPulseDelay();
        Color color = GearInLunaSettingsReader.getMarkerColor();
        String label = GearInLunaSettingsReader.getLabelText();
        engine.maintainStatusForPlayerShip(this, null, label,
                "markers=" + enabled + " limit=" + maxEntries + " delay=" + delay, false);
        if (!enabled) {
            elapsed = 0f;
            return;
        }

        elapsed += amount;
        if (elapsed < delay) {
            return;
        }
        elapsed = 0f;
        List<ShipAPI> enemies = AIUtils.getNearbyEnemies(player, 1600f);
        int count = Math.min(maxEntries, enemies.size());
        for (int index = 0; index < count; index++) {
            engine.addSmoothParticle(enemies.get(index).getLocation(), new Vector2f(),
                    24f, 1f, 0.4f, color);
        }
    }
}

验证方案

前置数据:准备包含至少两艘可见敌舰的测试战斗,LunaLib 可在启动器启用与禁用。

  1. 保存 CSV、四个类、生涯调用与战斗插件登记,构建并重启,载入生涯确认注册和字段日志。
  2. 进入测试战斗,查看 GearIn 标签与默认粉色标记。返回生涯,在 LunaLib 面板把上限改为 1、间隔改为 0.5、颜色改为绿色、标签改为新文字,保存后再次进入测试战斗。
  3. 比较下一有效帧的标签、下一组粒子的颜色、每次目标数与间隔。关闭开关后观察新标记停止,再开启比较。
  4. 在面板尝试范围外的间隔,通过控件观察输入结果并保存,确认界面和实际读取对应;恢复 1.5。
  5. 重复读档,再实际修改并保存本 MOD 设置,核对每次变更通知只产生一组 reloaded 日志;修改并保存其他 MOD 时比较本 MOD 日志。
  6. 禁用 LunaLib 后重启,进入同一基础内容和测试战斗,核对 Defs 默认及类加载。

预期结果:

  • 本节五个字段与前文图形开关共同出现,拥有者为 gear_in;
  • 保存后标签采用当前文字,粒子采用新颜色、数量与间隔;
  • 关闭时停止新增,已有粒子在 0.4 秒内结束;
  • 本 MOD 保存只通知当前登记的一份监听,读档保持注册状态;
  • 库缺席时四个自有角色按默认运行,当前默认为 true、12、1.5、粉色和 GearIn。

故障分析

  • 库缺席时调用监听器出现类加载异常:外层字段或接口直接引用 LunaLib 类型。

    • 解决方案:采用本节嵌套适配器,把接口实例留在启用分支后的类中。
  • 字段读取回到默认:拥有者、字段名或保存类型不匹配。

    • 解决方案:核对 gear_in、Defs 的键和 LunaLib 取值日志,恢复 CSV 中的定义。
  • 面板没有本节控件:路径、必需列或字段类型无法建立控件。

    • 解决方案:核对九列表头与实际空列,保存后重启。
  • 日志更新但画面保持此前参数:效果仍保存旧副本或没有调用当前读取器。

    • 解决方案:核对战斗插件登记和五个 getter 的使用位置。
  • 保存后回调次数增加:同类监听器被重复登记。

    • 解决方案:按 registered 类查询已存在实例,再加入监听。
  • 关闭后仍短暂看到此前粒子:已生成粒子继续按自身寿命显示。

    • 解决方案:观察新生成停止,并等待本节 0.4 秒粒子结束。

总结

面板更新已加载设置,读取器按拥有者和字段取得值,战斗插件再将它们用于当前显示与粒子生成。通知在旁记录保存结果;即时生效来自实际读取,已有粒子与累计计时仍遵循自己的状态。库缺席时采用同一组缺省,使用方的工作继续成立。

建议参考项

  • data/config/LunaSettings.csv:控件与范围定义;
  • lunalib.lunaSettings.LunaSettings:包装值读取与监听器登记;
  • lunalib.lunaSettings.LunaSettingsListener:保存通知;
  • com.fs.starfarer.api.combat.CombatEngineAPI:玩家状态标签和粒子;
  • org.lazywizard.lazylib.combat.AIUtils:可见敌舰候选。
在 GitHub 上编辑此页
上次更新: 2026/10/14 12:25
贡献者: 1847905557
上一页
为 Nexerelin 特遣队添加命名与事件响应