【进阶教程】派蒙手把手教你之第二弹:详解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
(待施工)
前排围观{:5_118:} 那我后排吃瓜{:tieba_12:} {:5_123:}....
static 是静态修饰符,允许在不创建实例的情况下访问
不允许二次赋值的是final cjy4312 发表于 2023-6-23 22:10
....
static 是静态修饰符,允许在不创建实例的情况下访问
不允许二次赋值的是final ...
感谢提醒,已修正 牛哇牛哇{:tieba_43:} 大佬有空讲一期单纯制作殖民地建筑的模组? 说起来如果大佬有空可不可以讲一期星系生成的教程awa,狠狠的顶啦! 卢左和欧米茄大型感人剧情进度+1。安超典范和安超欧米茄究竟会碰撞出怎样的火花。
页:
[1]