P.A.I.M.O.N 发表于 2023-6-23 20:41:42

【进阶教程】派蒙手把手教你之第二弹:详解Intel

本帖最后由 P.A.I.M.O.N 于 2023-6-23 22:20 编辑

大家好我是派蒙,今天又来给各位星辰旅行者讲课了,上次有人说我只说加能接任务的NPC和Rules,没说怎么写任务,那不是只给开瓶器不给啤酒么?
嘿嘿,小派蒙是个诚实的孩子,说加NPC就加NPC,一点不多,一点也不少,Intel放在这期讲。
同时,因为议长向我提出建议,说教程最好一步一步写,否则大量信息直接贴出来会让读者无所适从,尤其是那个Rules教程可能会卡住大部分读者,所以写intel的计划刻不容缓了!
那么废话说够了,开始!
1:建立一个新的*.Java文件
打开Starsector\starsector-core\starfarer.api\com\fs\starfarer\api\impl\campaign\missions,这是原版任务文件的储存地点,我们以原版为蓝本,复制粘贴一个文件到我们的MOD之中。
这里我选择以SpySatDeployment为原型,重新制作一次我X入侵中watchmen_Scout玩家去异械老家回收侦查信标的任务。
将文件复制到XInvade(你的MOD文件夹)\com\fs\starfarer\api\impl\campaign\missions中,改名并把文件中无关内容删除,变成以下模样
package com.fs.starfarer.api.impl.campaign.missions;

import java.awt.Color;

import com.fs.starfarer.api.campaign.SectorEntityToken;
import com.fs.starfarer.api.campaign.econ.MarketAPI;
import com.fs.starfarer.api.characters.PersonAPI;
import com.fs.starfarer.api.impl.campaign.ids.Factions;
import com.fs.starfarer.api.impl.campaign.ids.Ranks;
import com.fs.starfarer.api.impl.campaign.ids.Tags;
import com.fs.starfarer.api.impl.campaign.missions.hub.HubMissionWithBarEvent;
import com.fs.starfarer.api.ui.TooltipMakerAPI;
import com.fs.starfarer.api.util.Misc;
//上方的import是导入类,只有导入了某个类才能使用它的方法,一般来说如果你技术不精湛,不要随意删除上面的内容
public class watchmen_Scout extends HubMissionWithBarEvent {
//类名与继承,注意类名应与文件名一致,以你MOD的特殊标识符为开头。因为这里是守望者联盟而非异械的任务,所以我用了watchmen

public enum Stage {//任务的阶段

}

@Override
protected boolean create(MarketAPI createdAt, boolean barEvent) {//当任务被创建时自动调用

return true;
}

protected void updateInteractionDataImpl() {//当任务被手动刷新时(例如在rules里使用Call XXXXX updateData)时调用

}

@Override
public void addDescriptionForNonEndStage(TooltipMakerAPI info, float width, float height) {//在intel界面里,提示玩家任务的下一步该做什么

}

@Override
public boolean addNextStepText(TooltipMakerAPI info, Color tc, float pad) {//任务刷新至下阶段时,弹出的“子弹信息”内容

}

@Override
public String getBaseName() {//在各处显示给玩家看的任务名称,可自定义
return "侦察计划";
}

}
2:设计任务

在编写Intel前,你要在脑海中大致规划一下该任务讲述的是什么故事,流程如何。此处与代码设计没有什么关系,更多是考验作者的策划能力。
在XInvade中,watchmen_Scout侦察计划任务是守望者联盟的导师Marx,因为最近异械没有动静怀疑这些邪恶机械是不是在谋划什么,于是发射了探测信标。可能由于距离太远,信标发生了故障,不再向他发送信号,需要探险者手动去把信标回收,遂派玩家不远千里前往Xenon Sector 101星区。当玩家到达信标附近时才发现,信标早就被异械捕获,敌人舰队在某处守株待兔等待傻白甜玩家自投罗网,怎想玩家战斗力爆表,最终成功回收信标,结局皆大欢喜。
3:设置任务阶段与初始化
根据上个阶段的规划,任务总计分为4个阶段,可以用枚举来表示它们。关于枚举的本质,感兴趣的读者可以自行寻找Java基础教程深入了解。
public enum Stage {
    GO_TO_PROBE,//从任务开始,到玩家回收信标
    RETURN_TO_CHALDEA,//从玩家回收信标,到玩家回到Marx身边交付任务
    COMPLETED,//任务完成
    FAILED,//任务失败
}原则上,一个任务至少需要有三个阶段:开始阶段,成功阶段,失败阶段。这三个阶段可以在create函数中用以下方法设置:
@Override
protected boolean create(MarketAPI createdAt, boolean barEvent) {//当任务被创建时自动调用
    setStartingStage(Stage.GO_TO_PROBE);
    addSuccessStages(Stage.COMPLETED);
    addFailureStages(Stage.FAILED);
    return true;
}在正式编写create函数之前,我们要首先做一些准备工作,除了设置静态与全局变量外,还要设置任务ref:
public class watchmen_Scout extends HubMissionWithSearch {

public static float MISSION_DAYS = 60f;

public enum Stage {
    GO_TO_PROBE,
    RETURN_TO_CHALDEA,
    COMPLETED,
    FAILED,
}

protected StarSystemAPI system;
protected SectorEntityToken object;

@Override
protected boolean create(MarketAPI createdAt, boolean barEvent) {
    if (!setGlobalReference("$watchmenScout_ref")) {
    //上面这句话含义是,初始化一个Global索引,在rules中可以根据该索引找到此实例化的Intel,该索引名字为"$watchmenScout_ref"。
    //如果创建失败,则返回false,也就是说如果创建失败,那么if里面的值应该为true,如果为true,则执行下面的return false,创建失败,直接结束,避免出BUG
      return false;
    }
    object = Global.getSector().getEntityById("xenon_sector101debris");
    //从Global中根据ID"xenon_sector101debris"找到一个物体,其实是我提前在xenonsector101星系文件中设置好的残骸场

    if (object == null) {//如果找不到该物体,则说明出了BUG,直接返回,避免游戏崩溃
      return false;
    }
    system = object.getStarSystem();//通过残骸场,找到残骸场所处的星系,也就是“xenonsector101”

    setStartingStage(Stage.GO_TO_PROBE);
    addSuccessStages(Stage.COMPLETED);
    addFailureStages(Stage.FAILED);
    return true;
}4:设置任务细节

将以下代码段加到return true的前面,addFailureStages后面
这些是对任务一些琐碎细节的设置
SectorEntityToken probe = spawnEntity(Entities.GENERIC_PROBE, new LocData(object));
//这句的含义是创建一个物体,这个物体的种类是Entities.GENERIC_PROBE(探测器)
//类Entities里有各种类型的物体,例如Entities.COMM_RELAY是通讯阵列
//物体的位置是new LocData(object),之所以要创建一个LocData是因为这个函数的参数如此,道理和设置颜色时用new Color(255,255,255,255)而不直接用(255,255,255,255)这种RGB参数一样。
//这个位置内容就是object的位置,也就是说探测器会出现在残骸场的正中央。

if (probe == null) return false;//如果创建失败,立刻结束以免出现更大的BUG导致游戏崩溃

probe.setId("watchmenScout_probe");//设置该物体的ID为括号中内容,该步骤不是强制的,可以省略。

makeImportant(probe, "$watchmenScout_probeFlag", Stage.GO_TO_PROBE);
//设置该物体为重要物体,直观来看就是在右上角添加一个黄色感叹号,三个参数分别为需要添加感叹号的物体,以什么索引添加(索引可能在其他地方用到,此处为null亦可),在什么阶段添加。
//如果阶段为Stage.GO_TO_PROBE,那么在阶段Stage.RETURN_TO_CHALDEA则感叹号不会出现。

makeImportant(getPerson(), "$watchmenScout_returnHere", Stage.RETURN_TO_CHALDEA);
//设置人物为重要人物。默认状态下,getPerson()方法返回的人物是派发给玩家任务的人物,这里即Marx

setStageOnGlobalFlag(Stage.RETURN_TO_CHALDEA, "$watchmenScout_canReturn");
//设置一个GlobalFlag变量,当该变量为true时更新任务会直接跳转到某阶段,例如在rules中使用"Call xxxxx updateStage"时。

connectWithGlobalFlag(Stage.RETURN_TO_CHALDEA, Stage.COMPLETED, "$watchmenScout_finished");
//设置一个GlobalFlag变量,当该变量为true且当前阶段处于A阶段时更新任务会跳转到B阶段。也就是说,如果阶段不为Stage.RETURN_TO_CHALDEA,即使$watchmenScout_finished值为true时使用Call,也不会刷新阶段。

setStageOnGlobalFlag(Stage.FAILED, "$watchmenScout_failed");
//同上,一般来说,setStageOnGlobalFlag可以满足绝大部分需求。

setTimeLimit(Stage.FAILED, MISSION_DAYS, system, Stage.RETURN_TO_CHALDEA);
//设置时间限制,当过了MISSION_DAYS(120)天后,会自动跳转到FAILED阶段,即任务失败。
//当玩家处于system星系中时,不会因时间问题被宣告任务失败。当任务阶段为RETURN_TO_CHALDEA时,即使超过了120天,也不会被强制任务失败。

setCreditReward(CreditReward.AVERAGE);//设置任务完成后获得的星币量大小
setRepRewardPerson(RepRewards.VERY_HIGH);//设置任务完成后,派发任务的NPC关系增加量
setRepRewardFaction(RepRewards.EXTREME);//设置任务完成后,NPC所属阵营的关系增加量
5:设置Trigger

现在,我们要进行重头戏——任务中,埋伏玩家的舰队的设置
我们可以用Alex准备好的接口——Trigger来进行设置,这些Trigger详情可以在类HubMissionWithTrigger中看到。
除此之外,我们也可以使用更加基础的FleetParamV3之类接口来设置舰队,但是此处我推荐使用更加方便的Trigger
beginInRangeOfEntityTrigger(object, 100f,Stage.GO_TO_PROBE);
      //设置触发条件,这里是当玩家舰队距离object(残骸场)距离小于100f,且阶段处于GO_TO_PROBE时触发
      triggerCreateFleet(FleetSize.MEDIUM, FleetQuality.VERY_HIGH, "xenon", FleetTypes.PATROL_MEDIUM, object);
      //创建舰队,参数分别从左到右为舰队规模,舰队质量,阵营,舰队类型,大致位置
      triggerFleetNoJump();
      //令trigger创造出的舰队无法超空间跳跃
      triggerSetFleetMissionRef("$watchmenScout_ref");
      //设置舰队所属任务,一般用不上
      triggerFleetSetPatrolActionText("等待");
      //设置舰队在巡逻时,玩家鼠标移到舰队上显示出的介绍文字
      triggerOrderFleetPatrol(probe);
      //令舰队在probe(探测器)附近巡逻
      triggerOrderFleetInterceptPlayer();
      //令舰队在探测器扫到玩家时追击玩家
      triggerPickLocationAroundEntity(probe, 500f);
      //挑选一个位置,为后续生成做准备,这里是挑选距离probe探测器半径500f的圆弧上的位置
      triggerSpawnFleetAtPickedLocation("$watchmenScout_EnemyFlag", "watchmenScout_ref");
      //生成舰队在挑选的位置,并赋予flag和任务所属。注意,如果只有上文的CreateFleet舰队只会被创建在内存中,如果不生成玩家是无法在生涯模式宇宙中与其遭遇的。
      triggerFleetMakeImportant("$watchmenScout_Enemy",Stage.RETURN_TO_CHALDEA);
      //设置该舰队为重要舰队
      endTrigger();
      //结束Trigger
如果之前按部就班,那么现在您的create函数内应该是如下景象:
@Override
protected boolean create(MarketAPI createdAt, boolean barEvent) {
    if (!setGlobalReference("$watchmenScout_ref")) {
      return false;
    }
    object = Global.getSector().getEntityById("xenon_sector101debris");
    if (object == null) {
      return false;
    }
    system = object.getStarSystem();
               
    setStartingStage(Stage.GO_TO_PROBE);
    addSuccessStages(Stage.COMPLETED);
    addFailureStages(Stage.FAILED);

    SectorEntityToken probe = spawnEntity(Entities.GENERIC_PROBE, new LocData(object));
    if (probe == null) return false;
               
    probe.setId("watchmenScout_probe");
    makeImportant(probe, "$watchmenScout_probe", Stage.GO_TO_PROBE);
    makeImportant(getPerson(), "$watchmenScout_returnHere", Stage.RETURN_TO_CHALDEA);

    setStageOnGlobalFlag(Stage.RETURN_TO_CHALDEA, "$watchmenScout_canReturn");
    connectWithGlobalFlag(Stage.RETURN_TO_CHALDEA, Stage.COMPLETED, "$watchmenScout_finished");
    setStageOnGlobalFlag(Stage.FAILED, "$watchmenScout_failed");

    setTimeLimit(Stage.FAILED, MISSION_DAYS, system, Stage.RETURN_TO_CHALDEA);
    setCreditReward(CreditReward.AVERAGE);
    setRepRewardPerson(RepRewards.VERY_HIGH);
    setRepRewardFaction(RepRewards.EXTREME);

    beginInRangeOfEntityTrigger(object, 100f, Stage.GO_TO_PROBE);
    triggerCreateFleet(FleetSize.MEDIUM, FleetQuality.VERY_HIGH, "xenon", FleetTypes.PATROL_MEDIUM, object);
    triggerFleetNoJump();
    triggerSetFleetMissionRef("$watchmenScout_ref");
    triggerFleetSetPatrolActionText("等待");
    triggerOrderFleetPatrol(probe);
    triggerOrderFleetInterceptPlayer();
    triggerPickLocationAroundEntity(probe, 500f);
    triggerSpawnFleetAtPickedLocation("$watchmenScout_EnemyFlag", "watchmenScout_ref");
    triggerFleetMakeImportant("$watchmenScout_Enemy", Stage.RETURN_TO_CHALDEA);
    endTrigger();
    return true;
}
5:设置Update行为及面向玩家的描述

函数“updateInteractionDataImpl()”将在玩家使用Call XXXXX updateData时被调用一次
protected void updateInteractionDataImpl() {
    if (getCurrentStage() != null) {
      set("$watchmenScout_stage", ((Enum)getCurrentStage()).name());//这个是防止出BUG用的
    }
    set("$watchmenScout_starName", system.getNameWithNoType());//星系名字
    set("$watchmenScout_systemName", system.getNameWithLowercaseTypeShort());//星系名字的小写
    set("$watchmenScout_dist", getDistanceLY(object));//星系距离玩家当前位置的距离
    set("$watchmenScout_reward", Misc.getWithDGS(getCreditsReward()));//获得报酬的量(星币)
}这些一般用于rules中获取任务信息(报酬量,距离,关键物体所在星系之类)

最后,我们在addDescriptionForNonEndStage和addNextStepText中设置任务在intel和左下角弹出的简报中的描述文字
@Override
      public void addDescriptionForNonEndStage(TooltipMakerAPI info, float width, float height) {
                float opad = 10f;
                Color h = Misc.getHighlightColor();
                if (currentStage == Stage.GO_TO_PROBE) {
                        info.addPara("恢复在 " +
                                                system.getNameWithLowercaseTypeShort() + " 星系中探测器里的数据包.", opad);
                } else if (currentStage == Stage.RETURN_TO_CHALDEA) {
                        info.addPara("将数据包送回Chaldea并与" +
                                        getPerson().getNameString() + " 谈话获取你的报酬.", opad);
                }
      }

      @Override
      public boolean addNextStepText(TooltipMakerAPI info, Color tc, float pad) {
                Color h = Misc.getHighlightColor();
                if (currentStage == Stage.GO_TO_PROBE) {
                        if (system.isCurrentLocation()) {
                              info.addPara("恢复探测器里的数据包", tc, pad);
                        } else {
                              info.addPara(getGoToSystemTextShort(system), tc, pad);
                        }
                        return true;
                } else if (currentStage == Stage.RETURN_TO_CHALDEA) {
                        info.addPara("回到Chaldea并与" + getPerson().getNameString() + " 对话", tc, pad);
                        return true;
                }
                return false;
      }这两个函数之中的参数不必考虑太多,只需修改addPara中的内容即可。
最后的工作完成,如果中间步骤没有出错的话,类中内容应该如下所示:
package com.fs.starfarer.api.impl.campaign.missions;

import java.awt.Color;
import java.util.List;
import java.util.Map;
import com.fs.starfarer.api.Global;
import com.fs.starfarer.api.campaign.InteractionDialogAPI;
import com.fs.starfarer.api.campaign.SectorEntityToken;
import com.fs.starfarer.api.campaign.StarSystemAPI;
import com.fs.starfarer.api.campaign.econ.MarketAPI;
import com.fs.starfarer.api.campaign.rules.MemoryAPI;
import com.fs.starfarer.api.impl.campaign.CoreReputationPlugin.RepRewards;
import com.fs.starfarer.api.impl.campaign.ids.Entities;
import com.fs.starfarer.api.impl.campaign.ids.FleetTypes;
import com.fs.starfarer.api.impl.campaign.missions.hub.HubMissionWithSearch;
import com.fs.starfarer.api.ui.TooltipMakerAPI;
import com.fs.starfarer.api.util.Misc;
import com.fs.starfarer.api.util.Misc.Token;

public class watchmen_Scout extends HubMissionWithSearch {

      public static float MISSION_DAYS = 60f;
      
      public enum Stage {
                GO_TO_PROBE,
                RETURN_TO_CHALDEA,
                COMPLETED,
                FAILED,
      }
      
      protected StarSystemAPI system;
      protected SectorEntityToken object;
      
      @Override
      protected boolean create(MarketAPI createdAt, boolean barEvent) {
                // if this mission type was already accepted by the player, abort
                if (!setGlobalReference("$watchmenScout_ref")) {
                        return false;
                }
                object = Global.getSector().getEntityById("xenon_sector101debris");
                if (object == null) {
                        return false;
                }
                system = object.getStarSystem();
               
                setStartingStage(Stage.GO_TO_PROBE);
                addSuccessStages(Stage.COMPLETED);
                addFailureStages(Stage.FAILED);

                SectorEntityToken probe = spawnEntity(Entities.GENERIC_PROBE, new LocData(object));
                if (probe == null) return false;
               
                probe.setId("watchmenScout_probe");
                makeImportant(probe, "$watchmenScout_probe", Stage.GO_TO_PROBE);
                makeImportant(getPerson(), "$watchmenScout_returnHere", Stage.RETURN_TO_CHALDEA);

                setStageOnGlobalFlag(Stage.RETURN_TO_CHALDEA, "$watchmenScout_canReturn");
                connectWithGlobalFlag(Stage.RETURN_TO_CHALDEA, Stage.COMPLETED, "$watchmenScout_finished");
                setStageOnGlobalFlag(Stage.FAILED, "$watchmenScout_failed");

                setTimeLimit(Stage.FAILED, MISSION_DAYS, system, Stage.RETURN_TO_CHALDEA);
                setCreditReward(CreditReward.AVERAGE);
                setRepRewardPerson(RepRewards.VERY_HIGH);
                setRepRewardFaction(RepRewards.EXTREME);

                beginInRangeOfEntityTrigger(object, 100f, Stage.GO_TO_PROBE);
                triggerCreateFleet(FleetSize.MEDIUM, FleetQuality.VERY_HIGH, "xenon", FleetTypes.PATROL_MEDIUM, object);
                triggerFleetNoJump();
                triggerSetFleetMissionRef("$watchmenScout_ref");
                triggerFleetSetPatrolActionText("等待"); // a bit dark, maybe?
                triggerOrderFleetPatrol(probe);
                triggerOrderFleetInterceptPlayer();
                triggerPickLocationAroundEntity(probe, 500f);
                triggerSpawnFleetAtPickedLocation("$watchmenScout_EnemyFlag", "watchmenScout_ref");
                triggerFleetMakeImportant("$watchmenScout_Enemy", Stage.RETURN_TO_CHALDEA);
                endTrigger();
                return true;
      }
      
      @Override
      protected boolean callAction(String action, String ruleId, InteractionDialogAPI dialog, List<Token> params, Map<String, MemoryAPI> memoryMap) {
                return false;
      }

      protected void updateInteractionDataImpl() {
                if (getCurrentStage() != null) {
                        set("$watchmenScout_stage", ((Enum)getCurrentStage()).name());
                }
                set("$watchmenScout_starName", system.getNameWithNoType());
                set("$watchmenScout_systemName", system.getNameWithLowercaseTypeShort());
                set("$watchmenScout_dist", getDistanceLY(object));
                set("$watchmenScout_reward", Misc.getWithDGS(getCreditsReward()));
      }

      @Override
      public void addDescriptionForNonEndStage(TooltipMakerAPI info, float width, float height) {
                float opad = 10f;
                Color h = Misc.getHighlightColor();
                if (currentStage == Stage.GO_TO_PROBE) {
                        info.addPara("恢复在 " +
                                                system.getNameWithLowercaseTypeShort() + " 星系中探测器里的数据包.", opad);
                } else if (currentStage == Stage.RETURN_TO_CHALDEA) {
                        info.addPara("将数据包送回Chaldea并与" +
                                        getPerson().getNameString() + " 谈话获取你的报酬.", opad);
                }
      }

      @Override
      public boolean addNextStepText(TooltipMakerAPI info, Color tc, float pad) {
                Color h = Misc.getHighlightColor();
                if (currentStage == Stage.GO_TO_PROBE) {
                        if (system.isCurrentLocation()) {
                              info.addPara("恢复探测器里的数据包", tc, pad);
                        } else {
                              info.addPara(getGoToSystemTextShort(system), tc, pad);
                        }
                        return true;
                } else if (currentStage == Stage.RETURN_TO_CHALDEA) {
                        info.addPara("回到Chaldea并与" + getPerson().getNameString() + " 对话", tc, pad);
                        return true;
                }
                return false;
      }
      
      @Override
      public String getBaseName() {
                return "Recover Scouting Datapack";
      }
      
}6:Q&A

(待施工)





左径壬Alopika 发表于 2023-6-23 21:06:13

前排围观{:5_118:}

农工育三业 发表于 2023-6-23 21:52:46

那我后排吃瓜{:tieba_12:}

cjy4312 发表于 2023-6-23 22:10:49

{:5_123:}....
static 是静态修饰符,允许在不创建实例的情况下访问
不允许二次赋值的是final

P.A.I.M.O.N 发表于 2023-6-23 22:19:45

cjy4312 发表于 2023-6-23 22:10
....
static 是静态修饰符,允许在不创建实例的情况下访问
不允许二次赋值的是final ...

感谢提醒,已修正

極光 发表于 2023-6-24 01:46:57

牛哇牛哇{:tieba_43:}

天启洪流Apo 发表于 2023-7-25 16:38:03

大佬有空讲一期单纯制作殖民地建筑的模组?

左径壬Alopika 发表于 2023-8-17 10:32:07

说起来如果大佬有空可不可以讲一期星系生成的教程awa,狠狠的顶啦!

大地正义 发表于 2023-10-18 11:09:20

卢左和欧米茄大型感人剧情进度+1。安超典范和安超欧米茄究竟会碰撞出怎样的火花。
页: [1]
查看完整版本: 【进阶教程】派蒙手把手教你之第二弹:详解Intel