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 渲染方案

配置武器脚本

武器持续开火、弹体生成、命中与光束照射,会在不同位置调用脚本。本节将这些入口分别绑定到都林和乌里,用日志记录武器状态、射弹来源与伤害结算,再以光束末端的粒子对应逐帧信息。每段逻辑都使用本次回调提供的对象。

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

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

学习目标

完成后,您将能够:

  • 选择 wpn 或 proj 的对应脚本字段;
  • 读取当前武器、射弹、命中与光束参数;
  • 区分每实例状态和单次事件;
  • 用日志与粒子核对调用。

前置知识

  • [C1-S5] 的都林弹体与乌里光束;
  • [C2-S4] 的效果登记;
  • [C6-S4] 的编译与 JAR;
  • [C8-S2] 的对象和帧状态;
  • [C8-S3] 的逐帧与暂停。

将脚本绑定到实际使用的数据

武器脚本字段速查表

字段接口当前用途
wpn.everyFrameEffectEveryFrameWeaponEffectPlugin读取当前武器的持续状态
proj.onFireEffectOnFireEffectPlugin弹体生成后的事件
proj.onHitEffectOnHitEffectPlugin当前命中与结算
wpn.beamEffectBeamEffectPlugin激活光束的逐帧信息

.wpn 绑定武器与光束逻辑,.proj 绑定弹体事件。 完整类名指向 JAR 中的实现,但还要由当前武器实际使用这份数据。特别是生成与命中脚本,登记在武器引用的弹体文件里,事件才能到达对应类。

记录武器实例的开火过程

在都林的 .wpn 中,将每帧效果绑定到 GearInWeaponFlowEffect。局部字段如下:

"everyFrameEffect": "data.scripts.gearin.combat.GearInWeaponFlowEffect"

每把武器拥有自己的效果实例,所以它的计时器与 wasFiring 只跟踪当前这把武器。绑定时,WeaponEffectPluginWithInit.init(...) 记录武器标识、规格与光束分类。

随后在 advance(...) 中比较当前与上次开火状态。由未开火转为开火时,记录一次变化,再保存本帧状态:

if (weapon.isFiring() && !wasFiring) {
    LOG.info("GearIn weapon " + weapon.getId() + " started firing"
            + " (before-frame callback)");
}
wasFiring = weapon.isFiring();

这个比较只在开始开火时产生日志。持续射击中的弹药、冷却与蓄力,放到 EveryFrameWeaponEffectPluginWithAdvanceAfter.advanceAfter(...) 记录;本例采用 2 秒报告间隔,避免连续输出淹没开火变化。

同一帧操作前后,读到的武器状态可能不同。 advanceAfter(...) 提供武器本帧操作完成后的观察位置,因此两个方法分别记录变化与执行后的结果。

在生成回调处理本次弹体

都林引用的是 GearIn_durin_shot.proj,将生成脚本登记在这份文件中:

"onFireEffect": "data.scripts.gearin.combat.GearInShotOnFireEffect"

onFire(...) 收到刚生成的 projectile、来源 weapon 与本场 engine。规格、伤害、EMP 与来源都可以在这个时机读取,生成位置也可用于安排单次表现。接口参数为:

void onFire(DamagingProjectileAPI projectile,
            WeaponAPI weapon,
            CombatEngineAPI engine)

完整例将这些信息写入同一条记录。来源武器或舰船可能因创建方式不同而缺席,日志保留相应空值。每次开火事件传入的是本次生成的弹体,下一发继续使用下一次参数。

从命中参数读取结算

在同一份弹体文件中登记命中效果:

"onHitEffect": "data.scripts.gearin.combat.GearInShotOnHitEffect"

弹体命中后,onHit(...) 接收目标、命中位置、护盾状态与 ApplyDamageResultAPI。shieldHit 标记是否命中护盾,damageResult 提供本次护盾、装甲、结构与 EMP 结算:

void onHit(DamagingProjectileAPI projectile,
           CombatEntityAPI target,
           Vector2f point,
           boolean shieldHit,
           ApplyDamageResultAPI damageResult,
           CombatEngineAPI engine)

射弹、导弹与光束脉冲的对应命中都可以沿弹体事件处理。目标与结果使用本次回调参数。 它们一起描述当前这笔命中,保留上一次的目标或结算来处理下一次事件,会混淆实际结果。

在照射期间读取光束

乌里光束的逐帧脚本登记在 .wpn:

"beamEffect": "data.scripts.gearin.combat.GearInBeamLogger"

光束激活期间,advance(...) 接收步长、引擎与 BeamAPI。起点、终点、长度、伤害和目标都从这次传入的光束读取:

void advance(float amount, CombatEngineAPI engine, BeamAPI beam)

完整类每累计 1 秒记录目标、didDamageThisFrame() 与长度,再在上一帧末端位置创建青色光点。日志对应帧状态,粒子对应读取的末端位置。光束停止激活后更新停止,下一次激活沿实例中的计时继续。

光束在画面中存在,不代表每一帧都产生伤害。 比较命中与未命中时,重新读取 getDamageTarget() 和 didDamageThisFrame(),才能把持续照射与本帧结算区分开。

完整最小例

四个类保存到 src/data/scripts/gearin/combat/,当前字段写入对应都林和乌里数据,保存后构建并重启。

完整成品:GearInWeaponFlowEffect
package data.scripts.gearin.combat;

import org.apache.log4j.Logger;

import com.fs.starfarer.api.Global;
import com.fs.starfarer.api.combat.CombatEngineAPI;
import com.fs.starfarer.api.combat.EveryFrameWeaponEffectPluginWithAdvanceAfter;
import com.fs.starfarer.api.combat.WeaponAPI;
import com.fs.starfarer.api.combat.WeaponEffectPluginWithInit;
import com.fs.starfarer.api.util.IntervalUtil;

/** 记录武器实例的初始化、开火变化和操作后状态。 */
public class GearInWeaponFlowEffect implements EveryFrameWeaponEffectPluginWithAdvanceAfter,
        WeaponEffectPluginWithInit {

    private static final Logger LOG = Global.getLogger(GearInWeaponFlowEffect.class);

    /** 开火状态 (每个武器效果实例各有一份)。 */
    private boolean wasFiring = false;
    private final IntervalUtil weaponReportInterval = new IntervalUtil(2f, 2f);

    @Override
    public void init(WeaponAPI weapon) {
        LOG.info("GearIn weapon init " + weapon.getId()
                + " spec=" + weapon.getSpec().getWeaponId()
                + " beam=" + weapon.isBeam());
    }

    @Override
    public void advance(float amount, CombatEngineAPI engine, WeaponAPI weapon) {
        if (weapon.isFiring() && !wasFiring) {
            LOG.info("GearIn weapon " + weapon.getId() + " started firing"
                    + " (before-frame callback)");
        }
        wasFiring = weapon.isFiring();
    }

    @Override
    public void advanceAfter(float amount, CombatEngineAPI engine, WeaponAPI weapon) {
        // 本帧武器操作完成后 (EveryFrameWeaponEffectPluginWithAdvanceAfter)
        weaponReportInterval.advance(amount);
        if (weapon.isFiring() && weaponReportInterval.intervalElapsed()) {
            LOG.info("GearIn weapon " + weapon.getId() + " after-frame:"
                    + " ammo=" + weapon.getAmmo() + "/" + weapon.getMaxAmmo()
                    + " cooldown=" + weapon.getCooldownRemaining()
                    + " charge=" + weapon.getChargeLevel()
                    + " firing=" + weapon.isFiring());
        }
    }
}
完整成品:GearInShotOnFireEffect
package data.scripts.gearin.combat;

import org.apache.log4j.Logger;

import com.fs.starfarer.api.Global;
import com.fs.starfarer.api.combat.CombatEngineAPI;
import com.fs.starfarer.api.combat.DamagingProjectileAPI;
import com.fs.starfarer.api.combat.OnFireEffectPlugin;
import com.fs.starfarer.api.combat.WeaponAPI;

/** 记录本次生成弹体的规格、来源与伤害。 */
public class GearInShotOnFireEffect implements OnFireEffectPlugin {

    private static final Logger LOG = Global.getLogger(GearInShotOnFireEffect.class);

    @Override
    public void onFire(DamagingProjectileAPI projectile, WeaponAPI weapon, CombatEngineAPI engine) {
        LOG.info("GearIn onFire " + projectile.getProjectileSpecId()
                + " weapon=" + (weapon == null ? "null" : weapon.getId())
                + " source=" + (projectile.getSource() == null ? "null" : projectile.getSource().getHullSpec().getHullId())
                + " spawnType=" + projectile.getSpawnType()
                + " damage=" + projectile.getDamageAmount()
                + " emp=" + projectile.getEmpAmount());
    }
}
完整成品:GearInShotOnHitEffect
package data.scripts.gearin.combat;

import org.apache.log4j.Logger;
import org.lwjgl.util.vector.Vector2f;

import com.fs.starfarer.api.Global;
import com.fs.starfarer.api.combat.CombatEngineAPI;
import com.fs.starfarer.api.combat.CombatEntityAPI;
import com.fs.starfarer.api.combat.DamagingProjectileAPI;
import com.fs.starfarer.api.combat.OnHitEffectPlugin;
import com.fs.starfarer.api.combat.listeners.ApplyDamageResultAPI;

/** 记录本次命中的目标、护盾与伤害结算结果。 */
public class GearInShotOnHitEffect implements OnHitEffectPlugin {

    private static final Logger LOG = Global.getLogger(GearInShotOnHitEffect.class);

    @Override
    public void onHit(DamagingProjectileAPI projectile, CombatEntityAPI target, Vector2f point,
            boolean shieldHit, ApplyDamageResultAPI damageResult, CombatEngineAPI engine) {
        LOG.info("GearIn onHit " + projectile.getProjectileSpecId()
                + " target=" + target.getClass().getSimpleName()
                + " shieldHit=" + shieldHit
                + " hull=" + damageResult.getDamageToHull()
                + " armor=" + damageResult.getTotalDamageToArmor()
                + " shields=" + damageResult.getDamageToShields()
                + " emp=" + damageResult.getEmpDamage());
    }
}
完整成品:GearInBeamLogger
package data.scripts.gearin.combat;

import java.awt.Color;

import org.apache.log4j.Logger;
import org.lwjgl.util.vector.Vector2f;

import com.fs.starfarer.api.Global;
import com.fs.starfarer.api.combat.BeamAPI;
import com.fs.starfarer.api.combat.BeamEffectPlugin;
import com.fs.starfarer.api.combat.CombatEngineAPI;

public class GearInBeamLogger implements BeamEffectPlugin {

    private static final Logger LOG = Global.getLogger(GearInBeamLogger.class);
    private static final float REPORT_INTERVAL = 1f;

    private float timeSinceLastReport;

    @Override
    public void advance(float amount, CombatEngineAPI engine, BeamAPI beam) {
        timeSinceLastReport += amount;
        if (timeSinceLastReport < REPORT_INTERVAL) {
            return;
        }
        timeSinceLastReport = 0f;
        String target = beam.getDamageTarget() == null
                ? "null"
                : beam.getDamageTarget().getClass().getSimpleName();
        LOG.info("GearIn beam tick target=" + target
                + " didDamageThisFrame=" + beam.didDamageThisFrame()
                + " length=" + beam.getLength());
        engine.addHitParticle(beam.getRayEndPrevFrame(), new Vector2f(),
                20f, 1f, 0.3f, Color.CYAN);
    }
}

验证方案

  1. 保存四个类与武器/弹体字段,构建并核对 class 条目。
  2. 在测试战斗使用都林,查看 weapon init 和操作前后状态。
  3. 发射并命中目标,比较 onFire 的生成参数与 onHit 的实际结算。
  4. 使用乌里光束,比较有目标、无目标和当前帧伤害,观察末端青色粒子。
  5. 暂停再恢复,核对实际回调和计时变化。
  6. 搜索四组前缀,按字段、数据文件与完整类名确认调用归属。

预期结果:

  • 每武器实例记录自己的 init、开火变化和操作后参数。
  • onFire 对应当前生成弹体,onHit 对应当前命中与结算。
  • 光束日志按当前帧读取目标和伤害,激活期间按秒记录。
  • 暂停时相关更新保持,恢复后继续当前状态。
  • 日志与视觉可以定位各入口实际使用的对象。

故障分析

  • .wpn 或 .proj 中没有出现任何对应日志:字段、文件类型或完整类名不正确时,游戏不会调用脚本。

    • 解决方案:检查字段名、文件类型和完整类名,确认 JAR 中存在对应的 .class 条目。
  • 日志出现 Problem loading class:注册值与 Java 类的实际名称不一致。

    • 解决方案:核对 package、类名、目录路径和注册字符串,重新构建 JAR。
  • EveryFrame 只有 advance 日志,操作后的状态没有记录:类没有实现带 advanceAfter 的接口,或没有实现对应方法。

    • 解决方案:让类实现 EveryFrameWeaponEffectPluginWithAdvanceAfter,并实现 advanceAfter(...)。
  • onFire 或 onHit 没有回调:脚本类没有绑定到测试弹体实际使用的 .proj。

    • 解决方案:把类名写入 .proj 的对应字段,确认测试武器实际使用了该弹体文件。
  • BeamEffect 在非光束武器上没有日志:beamEffect 只对光束武器的激活阶段生效。

    • 解决方案:只在光束武器的 .wpn 中配置 beamEffect,并在光束激活期间观察日志。
  • 光束日志显示上一帧目标或命中状态:回调中复用了上一帧的对象或状态。

    • 解决方案:在每次 advance(...) 中重新读取 didDamageThisFrame() 和 getDamageTarget(),不要跨帧保存对象。
  • 暂停后恢复时报告时间偏移:观察计时器在暂停帧继续累计。

    • 解决方案:在需要暂停一致性的外层战斗插件中检查 engine.isPaused(),只在非暂停帧累计观察时间。

总结

武器效果保存每把武器的持续状态,开火与命中回调提供本次弹体及结算,光束效果读取激活期间的帧信息。它们分别由武器和弹体数据中的字段接入。

都林与乌里的日志现在能够对应到这些时机。后续动画和复杂效果沿用同一组入口,在需要的事件中读取对象、计算结果并安排表现。

建议参考项

  • com.fs.starfarer.api.combat.EveryFrameWeaponEffectPlugin:武器级持续回调 (everyFrameEffect);
  • com.fs.starfarer.api.combat.BeamEffectPlugin:光束激活期间的逐帧回调 (beamEffect);
  • com.fs.starfarer.api.combat.OnFireEffectPlugin:射弹生成回调 (onFireEffect);
  • com.fs.starfarer.api.combat.OnHitEffectPlugin:当前命中上下文回调 (onHitEffect);
  • data/weapons/:原版武器与弹体定义,everyFrameEffect、beamEffect、onFireEffect、onHitEffect 注册字段的原版实例;
  • Starsector/starsector-core/starsector.log:核对回调日志的读取位置。
在 GitHub 上编辑此页
上次更新: 2026/10/14 12:25
贡献者: 1847905557
上一页
修改舰船数值
下一页
配置战斗监听器